/* * Copyright (c) 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * video.cpp * Framework for Sortix video drivers. */ #include #include #include #include #include #include #include #include #include #include #include namespace Sortix { namespace Video { static const uint64_t CONSOLE_DEVICE = 0; static const uint64_t CONSOLE_CONNECTOR = 0; static kthread_mutex_t video_lock = KTHREAD_MUTEX_INITIALIZER; struct DeviceEntry { char* name; VideoDevice* device; }; static size_t num_devices = 0; static size_t devices_length = 0; static DeviceEntry* devices = NULL; bool RegisterDevice(const char* name, VideoDevice* device) { ScopedLock lock(&video_lock); if ( num_devices == devices_length ) { size_t newdevices_length = devices_length ? 2 * devices_length : 8UL; DeviceEntry* newdevices = new DeviceEntry[newdevices_length]; if ( !newdevices ) return false; if ( devices ) { memcpy(newdevices, devices, sizeof(*devices) * num_devices); delete[] devices; } devices = newdevices; devices_length = devices_length; } char* drivername = String::Clone(name); if ( !drivername ) return false; size_t index = num_devices++; devices[index].name = drivername; devices[index].device = device; device->device_index = index; return true; } static bool SetVideoMode(VideoDevice* device, uint64_t connector, struct dispmsg_crtc_mode mode) { uint64_t device_index = device->device_index; TextBuffer* textbuf = NULL; if ( device_index == CONSOLE_DEVICE && connector == CONSOLE_CONNECTOR ) { if ( !(textbuf = device->CreateTextBuffer(connector, mode)) ) return false; Log::BeginReplace(); } if ( !device->SwitchMode(connector, mode) ) { if ( textbuf ) { Log::CancelReplace(); delete textbuf; } return false; } if ( textbuf ) { Log::FinishReplace(textbuf); Log::fallback_framebuffer = NULL; } return true; } bool ConfigureDevice(VideoDevice* device) { bool success = true; ScopedLock lock(&video_lock); uint64_t connectors_count = device->GetConnectorCount(); for ( uint64_t connector = 0; connector < connectors_count; connector++ ) { struct dispmsg_crtc_mode mode; if ( !device->GetDefaultMode(connector, &mode) || !SetVideoMode(device, connector, mode) ) success = false; } return success; } bool ResizeDisplay(uint64_t device_index, uint64_t connector, uint32_t xres, uint32_t yres, uint32_t bpp) { ScopedLock lock(&video_lock); if ( num_devices <= device_index ) return errno = ENODEV, false; DeviceEntry* device_entry = &devices[device_index]; VideoDevice* device = device_entry->device; // TODO: xres/yres/bpp == 0 means unchanged, so get current mode. if ( xres == 0 || yres == 0 || bpp == 0 ) return true; struct dispmsg_crtc_mode mode; memset(&mode, 0, sizeof(0)); mode.driver_index = 0; mode.magic = 0; mode.control = DISPMSG_CONTROL_VALID; mode.fb_format = bpp; mode.view_xres = xres; mode.view_yres = yres; mode.fb_location = 0; mode.pitch = xres * (bpp + 7) / 8; mode.surf_off_x = 0; mode.surf_off_y = 0; mode.start_x = 0; mode.start_y = 0; mode.end_x = 0; mode.end_y = 0; mode.desktop_height = yres; return SetVideoMode(device, connector, mode); } __attribute__((unused)) static bool TransmitString(struct dispmsg_string* dest, const char* str) { size_t size = strlen(str) + 1; size_t dest_size = dest->byte_size; dest->byte_size = size; if ( dest_size < size ) return errno = ERANGE, false; return CopyToUser(dest->str, str, size); } __attribute__((unused)) static char* ReceiveString(struct dispmsg_string* src) { if ( !src->byte_size ) return errno = EINVAL, (char*) NULL; char* ret = new char[src->byte_size]; if ( !ret ) return NULL; if ( !CopyFromUser(ret, src->str, src->byte_size) ) return NULL; if ( ret[src->byte_size-1] != '\0' ) { delete[] ret; return errno = EINVAL, (char*) NULL; } return ret; } static int EnumerateDevices(void* ptr, size_t size) { struct dispmsg_enumerate_devices msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); size_t requested_num_devices = msg.devices_length; msg.devices_length = num_devices; if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; if ( requested_num_devices < num_devices ) return errno = ERANGE, -1; for ( uint64_t i = 0; i < num_devices; i++ ) if ( !CopyToUser(&msg.devices[i], &i, sizeof(i)) ) return -1; return 0; } static int GetDriverCount(void* ptr, size_t size) { struct dispmsg_get_driver_count msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( num_devices <= msg.device ) return errno = ENODEV, -1; msg.driver_count = 1; if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; return 0; } static int GetDriverName(void* ptr, size_t size) { struct dispmsg_get_driver_name msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( num_devices <= msg.device ) return errno = ENODEV, -1; DeviceEntry* device_entry = &devices[msg.device]; if ( !TransmitString(&msg.name, device_entry->name) ) return -1; if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; return 0; } static int GetDriver(void* ptr, size_t size) { struct dispmsg_get_driver msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( num_devices <= msg.device ) return errno = ENODEV, -1; msg.driver_index = 0; if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; return 0; } static int SetDriver(void* ptr, size_t size) { struct dispmsg_set_driver msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( num_devices <= msg.device ) return errno = ENODEV, -1; if ( msg.driver_index != 0 ) return errno = EINVAL, -1; if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; return 0; } static struct dispmsg_crtc_mode GetLogFallbackMode() { struct dispmsg_crtc_mode mode; memset(&mode, 0, sizeof(mode)); mode.control = DISPMSG_CONTROL_VALID | DISPMSG_CONTROL_FALLBACK; mode.fb_format = Log::fallback_framebuffer_bpp; mode.view_xres = (uint32_t) Log::fallback_framebuffer_width; mode.view_yres = (uint32_t) Log::fallback_framebuffer_height; mode.fb_location = 0; mode.pitch = Log::fallback_framebuffer_width * Log::fallback_framebuffer_bpp / 8; return mode; } static int SetCrtcMode(void* ptr, size_t size) { struct dispmsg_set_crtc_mode msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( msg.device < num_devices ) { DeviceEntry* device_entry = &devices[msg.device]; VideoDevice* device = device_entry->device; if ( !SetVideoMode(device, msg.connector, msg.mode) ) return -1; } else if ( Log::fallback_framebuffer && msg.device == CONSOLE_DEVICE && msg.connector == CONSOLE_CONNECTOR ) { struct dispmsg_crtc_mode fallback_mode = GetLogFallbackMode(); if ( memcmp(&msg.mode, &fallback_mode, sizeof(msg.mode)) != 0 ) return errno = EINVAL, -1; } else { return errno = ENODEV, -1; } // No need to respond. return 0; } static int GetCrtcMode(void* ptr, size_t size) { struct dispmsg_get_crtc_mode msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); struct dispmsg_crtc_mode mode; if ( Log::fallback_framebuffer && msg.device == CONSOLE_DEVICE && msg.connector == CONSOLE_CONNECTOR ) { mode = GetLogFallbackMode(); } else if ( msg.device < num_devices ) { DeviceEntry* device_entry = &devices[msg.device]; VideoDevice* device = device_entry->device; if ( !device->GetCurrentMode(msg.connector, &mode) ) return -1; } else { return errno = ENODEV, -1; } msg.mode = mode; if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; return 0; } static int GetCrtcModes(void* ptr, size_t size) { struct dispmsg_get_crtc_modes msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); size_t nummodes = 0; struct dispmsg_crtc_mode* modes = NULL; if ( msg.device < num_devices ) { DeviceEntry* device_entry = &devices[msg.device]; VideoDevice* device = device_entry->device; modes = device->GetModes(msg.connector, &nummodes); if ( !modes ) return -1; } else if ( Log::fallback_framebuffer && msg.device == CONSOLE_DEVICE && msg.connector == CONSOLE_CONNECTOR ) { if ( !(modes = new struct dispmsg_crtc_mode[1]) ) return -1; modes[nummodes++] = GetLogFallbackMode(); } else { return errno = ENODEV, -1; } size_t requested_modes = msg.modes_length; msg.modes_length = nummodes; if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; if ( requested_modes < nummodes ) { delete[] modes; return errno = ERANGE, -1; } for ( size_t i = 0; i < nummodes; i++ ) { if ( !CopyToUser(&msg.modes[i], &modes[i], sizeof(modes[i])) ) { delete[] modes; return -1; } } delete[] modes; return 0; } static int GetMemorySize(void* ptr, size_t size) { struct dispmsg_get_memory_size msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( Log::fallback_framebuffer && msg.device == CONSOLE_DEVICE ) { msg.memory_size = Log::fallback_framebuffer_width * Log::fallback_framebuffer_height * Log::fallback_framebuffer_bpp / 8; } else if ( msg.device < num_devices ) { DeviceEntry* device_entry = &devices[msg.device]; VideoDevice* device = device_entry->device; msg.memory_size = device->FrameSize(); } else { return errno = ENODEV, -1; } DeviceEntry* device_entry = &devices[msg.device]; VideoDevice* device = device_entry->device; msg.memory_size = device->FrameSize(); if ( !CopyToUser(ptr, &msg, sizeof(msg)) ) return -1; return 0; } static int WriteMemory(void* ptr, size_t size) { struct dispmsg_write_memory msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( Log::fallback_framebuffer && msg.device == CONSOLE_DEVICE ) { size_t ideal_pitch = Log::fallback_framebuffer_width * Log::fallback_framebuffer_bpp / 8; if ( ideal_pitch == Log::fallback_framebuffer_pitch ) { size_t framesize = Log::fallback_framebuffer_height * Log::fallback_framebuffer_pitch; if ( framesize < msg.offset ) return 0; size_t count = msg.size; size_t left = framesize - msg.offset; if ( left < count ) count = left; const uint8_t* src = msg.src; uint8_t* dst = Log::fallback_framebuffer + msg.offset; if ( !CopyFromUser(dst, src, count) ) return -1; } else { const uint8_t* src = msg.src; size_t src_size = msg.size; uint64_t y = msg.offset / ideal_pitch; uint64_t x = msg.offset % ideal_pitch; while ( src_size && y < Log::fallback_framebuffer_height ) { size_t count = src_size; if ( ideal_pitch -x < count ) count = ideal_pitch - x; uint8_t* dst = Log::fallback_framebuffer + Log::fallback_framebuffer_pitch * y + x; if ( !CopyFromUser(dst, src, count) ) return -1; src += count; src_size -= count; x = 0; y++; } } } else if ( msg.device < num_devices ) { DeviceEntry* device_entry = &devices[msg.device]; VideoDevice* device = device_entry->device; ioctx_t ctx; SetupUserIOCtx(&ctx); if ( device->WriteAt(&ctx, msg.offset, msg.src, msg.size) < 0 ) return -1; } else { return errno = ENODEV, -1; } Log::Invalidate(); return 0; } static int ReadMemory(void* ptr, size_t size) { struct dispmsg_read_memory msg; if ( size != sizeof(msg) ) return errno = EINVAL, -1; if ( !CopyFromUser(&msg, ptr, sizeof(msg)) ) return -1; ScopedLock lock(&video_lock); if ( Log::fallback_framebuffer && msg.device == CONSOLE_DEVICE ) { size_t ideal_pitch = Log::fallback_framebuffer_width * Log::fallback_framebuffer_bpp / 8; if ( ideal_pitch == Log::fallback_framebuffer_pitch ) { size_t framesize = Log::fallback_framebuffer_height * Log::fallback_framebuffer_pitch; if ( framesize < msg.offset ) return 0; size_t count = msg.size; size_t left = framesize - msg.offset; if ( left < count ) count = left; const uint8_t* src = Log::fallback_framebuffer + msg.offset; uint8_t* dst = msg.dst; if ( !CopyToUser(dst, src, count) ) return -1; } else { uint8_t* dst = msg.dst; size_t dst_size = msg.size; uint64_t y = msg.offset / ideal_pitch; uint64_t x = msg.offset % ideal_pitch; while ( dst_size && y < Log::fallback_framebuffer_height ) { size_t count = dst_size; if ( ideal_pitch -x < count ) count = ideal_pitch - x; const uint8_t* src = Log::fallback_framebuffer + Log::fallback_framebuffer_pitch * y + x; if ( !CopyToUser(dst, src, count) ) return -1; dst += count; dst_size -= count; x = 0; y++; } } } else if ( msg.device < num_devices ) { DeviceEntry* device_entry = &devices[msg.device]; VideoDevice* device = device_entry->device; ioctx_t ctx; SetupUserIOCtx(&ctx); if ( device->ReadAt(&ctx, msg.offset, msg.dst, msg.size) < 0 ) return -1; } else { return errno = ENODEV, -1; } return 0; } } // namespace Video } // namespace Sortix namespace Sortix { int sys_dispmsg_issue(void* ptr, size_t size) { using namespace Video; struct dispmsg_header hdr; if ( size < sizeof(hdr) ) return errno = EINVAL, -1; if ( !CopyFromUser(&hdr, ptr, sizeof(hdr)) ) return -1; switch ( hdr.msgid ) { case DISPMSG_ENUMERATE_DEVICES: return EnumerateDevices(ptr, size); case DISPMSG_GET_DRIVER_COUNT: return GetDriverCount(ptr, size); case DISPMSG_GET_DRIVER_NAME: return GetDriverName(ptr, size); case DISPMSG_GET_DRIVER: return GetDriver(ptr, size); case DISPMSG_SET_DRIVER: return SetDriver(ptr, size); case DISPMSG_SET_CRTC_MODE: return SetCrtcMode(ptr, size); case DISPMSG_GET_CRTC_MODE: return GetCrtcMode(ptr, size); case DISPMSG_GET_CRTC_MODES: return GetCrtcModes(ptr, size); case DISPMSG_GET_MEMORY_SIZE: return GetMemorySize(ptr, size); case DISPMSG_WRITE_MEMORY: return WriteMemory(ptr, size); case DISPMSG_READ_MEMORY: return ReadMemory(ptr, size); default: return errno = ENOSYS, -1; } } } // namespace Sortix