/* * Copyright (c) 2018-2020 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "console.h" #include "di.h" #include "display/video_fb.h" static void *g_framebuffer = NULL; static bool g_display_initialized = false; static ssize_t console_write(struct _reent *r, void *fd, const char *ptr, size_t len); static void console_init_display(void); static const devoptab_t dotab_stdout = { .name = "con", .write_r = console_write, }; /* https://github.com/switchbrew/libnx/blob/master/nx/source/runtime/util/utf/decode_utf8.c */ static ssize_t decode_utf8(uint32_t *out, const uint8_t *in) { uint8_t code1, code2, code3, code4; code1 = *in++; if(code1 < 0x80) { /* 1-byte sequence */ *out = code1; return 1; } else if(code1 < 0xC2) { return -1; } else if(code1 < 0xE0) { /* 2-byte sequence */ code2 = *in++; if((code2 & 0xC0) != 0x80) { return -1; } *out = (code1 << 6) + code2 - 0x3080; return 2; } else if(code1 < 0xF0) { /* 3-byte sequence */ code2 = *in++; if((code2 & 0xC0) != 0x80) { return -1; } if(code1 == 0xE0 && code2 < 0xA0) { return -1; } code3 = *in++; if((code3 & 0xC0) != 0x80) { return -1; } *out = (code1 << 12) + (code2 << 6) + code3 - 0xE2080; return 3; } else if(code1 < 0xF5) { /* 4-byte sequence */ code2 = *in++; if((code2 & 0xC0) != 0x80) { return -1; } if(code1 == 0xF0 && code2 < 0x90) { return -1; } if(code1 == 0xF4 && code2 >= 0x90) { return -1; } code3 = *in++; if((code3 & 0xC0) != 0x80) { return -1; } code4 = *in++; if((code4 & 0xC0) != 0x80) { return -1; } *out = (code1 << 18) + (code2 << 12) + (code3 << 6) + code4 - 0x3C82080; return 4; } return -1; } static void console_init_display(void) { /* Initialize the display. */ display_init(); /* Set the framebuffer. */ display_init_framebuffer(g_framebuffer); /* Turn on the backlight after initializing the lfb */ /* to avoid flickering. */ display_backlight(true); /* Display is initialized. */ g_display_initialized = true; } static ssize_t console_write(struct _reent *r, void *fd, const char *ptr, size_t len) { size_t i = 0; if (!g_display_initialized && (g_framebuffer != NULL)) { console_init_display(); } while (i < len) { uint32_t chr; ssize_t n = decode_utf8(&chr, (uint8_t *)(ptr + i)); if (n == -1) { break; } else { i += (size_t)n; video_putc(chr > 0xFF ? '?' : (char)chr); } } return i; } static int console_create(void) { if (g_framebuffer != NULL) { errno = EEXIST; return -1; } /* Allocate memory for the framebuffer. */ g_framebuffer = memalign(0x1000, CONFIG_VIDEO_VISIBLE_ROWS * CONFIG_VIDEO_COLS * CONFIG_VIDEO_PIXEL_SIZE); if (g_framebuffer == NULL) { errno = ENOMEM; return -1; } devoptab_list[STD_OUT] = &dotab_stdout; devoptab_list[STD_ERR] = &dotab_stdout; setvbuf(stdout, NULL , _IOLBF, 4096); setvbuf(stderr, NULL , _IONBF, 0); return 0; } int console_init(void) { if (console_create() == -1) { return -1; } /* Zero-fill the framebuffer, etc. */ if (video_init(g_framebuffer) == -1) { errno = EIO; console_end(); return -1; } return 0; } void *console_get_framebuffer(void) { if (!g_display_initialized && (g_framebuffer != NULL)) { console_init_display(); } return g_framebuffer; } int console_display(const void *framebuffer) { if (!g_display_initialized && (g_framebuffer != NULL)) { console_init_display(); } display_init_framebuffer((void *)framebuffer); return 0; } int console_resume(void) { if (!g_display_initialized && (g_framebuffer != NULL)) { console_init_display(); } else { display_init_framebuffer(g_framebuffer); } return 0; } int console_end(void) { if (g_display_initialized) { /* Turn off the backlight. */ display_backlight(false); /* Terminate the display. */ display_end(); /* Display is terminated. */ g_display_initialized = false; } free(g_framebuffer); g_framebuffer = NULL; return 0; }