| /**************************************************************************** |
| * drivers/video/goldfish_gpu_fb.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <debug.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <nuttx/kthread.h> |
| #include <nuttx/video/fb.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/kmalloc.h> |
| |
| /**************************************************************************** |
| * Pre-processor definitions |
| ****************************************************************************/ |
| |
| #define EGL_RGB 0x1907 |
| #define EGL_RGBA 0x1908 |
| #define EGL_BGRA 0x80e1 |
| #define EGL_RGB565 0x8d62 |
| |
| #define EGL_UNSIGNED_BYTE 0x1401 |
| #define EGL_UNSIGNED_SHORT_5_6_5 0x8363 |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| enum |
| { |
| EGL_FB_WIDTH = 1, |
| EGL_FB_HEIGHT = 2, |
| }; |
| |
| enum |
| { |
| OP_GET_FB_PARAM = 10007, |
| OP_FB_POST = 10018, |
| OP_CREATE_COLOR_BUFFER = 10012, |
| OP_UPDATE_COLOR_BUFFER = 10024, |
| }; |
| |
| struct goldfish_gpu_fb_s |
| { |
| struct fb_vtable_s vtable; |
| struct fb_planeinfo_s planeinfo; |
| struct fb_videoinfo_s videoinfo; |
| struct file pipe; |
| int colorbuffer; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_getvideoinfo(FAR struct fb_vtable_s *vtable, |
| FAR struct fb_videoinfo_s *vinfo); |
| static int goldfish_gpu_fb_getplaneinfo(FAR struct fb_vtable_s *vtable, |
| int planeno, |
| FAR struct fb_planeinfo_s *pinfo); |
| static int goldfish_gpu_fb_vsync_thread(int argc, FAR char** argv); |
| static int goldfish_gpu_fb_init_pipe(FAR struct file *filep, |
| FAR const char *ns, |
| FAR const char *pipe_name, |
| int flags); |
| static int goldfish_gpu_fb_read_pipe(FAR struct file *pipe, |
| FAR void *buffer, |
| size_t size); |
| static int goldfish_gpu_fb_write_pipe(FAR struct file *pipe, |
| FAR const void *buffer, |
| size_t size); |
| static int goldfish_gpu_fb_get_param(FAR struct file *pipe, int type); |
| static int goldfish_gpu_fb_create_colorbuffer(FAR struct file *pipe, |
| int width, |
| int height, |
| int format); |
| static int goldfish_gpu_fb_update_colorbuffer(FAR struct file *pipe, |
| int colorbuffer, |
| int x, |
| int y, |
| int width, |
| int height, |
| int format, |
| int type, |
| FAR void *pixel, |
| size_t size); |
| static int goldfish_gpu_fb_post(FAR struct file *pipe, int colorbuffer); |
| static int goldfish_gpu_fb_commit(FAR struct goldfish_gpu_fb_s *fb, |
| FAR void * buf); |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_read_pipe |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_read_pipe(FAR struct file *pipe, |
| FAR void *buffer, |
| size_t size) |
| { |
| FAR char *p = (FAR char *)buffer; |
| |
| while (size > 0) |
| { |
| ssize_t n = file_read(pipe, p, size); |
| if (n < 0) |
| { |
| return n; |
| } |
| |
| p += n; |
| size -= n; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_write_pipe |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_write_pipe(FAR struct file *pipe, |
| FAR const void *buffer, |
| size_t size) |
| { |
| FAR const char *p = (FAR const char *)buffer; |
| |
| while (size > 0) |
| { |
| ssize_t n = file_write(pipe, p, size); |
| if (n < 0) |
| { |
| return n; |
| } |
| |
| p += n; |
| size -= n; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_init_pipe |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_init_pipe(FAR struct file *filep, |
| FAR const char *ns, |
| FAR const char *pipe_name, |
| int flags) |
| { |
| int zero_flag = 0; |
| char buf[256]; |
| int buf_len; |
| int ret; |
| |
| ret = file_open(filep, "/dev/goldfish_pipe", flags); |
| if (ret < 0) |
| { |
| gerr("Could not open /dev/goldfish_pipe: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| if (ns) |
| { |
| buf_len = snprintf(buf, sizeof(buf), "pipe:%s:%s", ns, pipe_name); |
| } |
| else |
| { |
| buf_len = snprintf(buf, sizeof(buf), "pipe:%s", pipe_name); |
| } |
| |
| ret = goldfish_gpu_fb_write_pipe(filep, buf, buf_len + 1); |
| if (ret < 0) |
| { |
| gerr("Could not connect to the '%s' error: %s", |
| buf, strerror(-ret)); |
| file_close(filep); |
| return ret; |
| } |
| |
| ret = goldfish_gpu_fb_write_pipe(filep, &zero_flag, sizeof(zero_flag)); |
| if (ret < 0) |
| { |
| gerr("Could not write zero flag to the '%s' error: %s", |
| buf, strerror(-ret)); |
| file_close(filep); |
| return ret; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_get_param |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_get_param(FAR struct file *pipe, int type) |
| { |
| int cmdbuf[3]; |
| int ret; |
| int res; |
| |
| cmdbuf[0] = OP_GET_FB_PARAM; |
| cmdbuf[1] = sizeof(cmdbuf); |
| cmdbuf[2] = type; |
| |
| ret = goldfish_gpu_fb_write_pipe(pipe, cmdbuf, sizeof(cmdbuf)); |
| if (ret < 0) |
| { |
| gerr("Write fb param cmd error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| ret = goldfish_gpu_fb_read_pipe(pipe, &res, sizeof(res)); |
| if (ret < 0) |
| { |
| gerr("Read fb param result error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| return res; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_post |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_post(FAR struct file *pipe, int colorbuffer) |
| { |
| int cmdbuf[3]; |
| int ret; |
| |
| cmdbuf[0] = OP_FB_POST; |
| cmdbuf[1] = sizeof(cmdbuf); |
| cmdbuf[2] = colorbuffer; |
| |
| ret = goldfish_gpu_fb_write_pipe(pipe, cmdbuf, sizeof(cmdbuf)); |
| if (ret < 0) |
| { |
| gerr("Write fb post cmd error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_create_colorbuffer |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_create_colorbuffer(FAR struct file *pipe, |
| int width, |
| int height, |
| int format) |
| { |
| int cmdbuf[5]; |
| int ret; |
| int res; |
| |
| cmdbuf[0] = OP_CREATE_COLOR_BUFFER; |
| cmdbuf[1] = sizeof(cmdbuf); |
| cmdbuf[2] = width; |
| cmdbuf[3] = height; |
| cmdbuf[4] = format; |
| |
| ret = goldfish_gpu_fb_write_pipe(pipe, cmdbuf, sizeof(cmdbuf)); |
| if (ret < 0) |
| { |
| gerr("Write create colorbuffer cmd error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| ret = goldfish_gpu_fb_read_pipe(pipe, &res, sizeof(res)); |
| if (ret < 0) |
| { |
| gerr("Read create colorbuffer result error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| return res; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_update_colorbuffer |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_update_colorbuffer(FAR struct file *pipe, |
| int colorbuffer, |
| int x, |
| int y, |
| int width, |
| int height, |
| int format, |
| int type, |
| FAR void *pixel, |
| size_t size) |
| { |
| int cmdbuf[10]; |
| int ret; |
| int res; |
| |
| cmdbuf[0] = OP_UPDATE_COLOR_BUFFER; |
| cmdbuf[1] = sizeof(cmdbuf) + size; |
| cmdbuf[2] = colorbuffer; |
| cmdbuf[3] = x; |
| cmdbuf[4] = y; |
| cmdbuf[5] = width; |
| cmdbuf[6] = height; |
| cmdbuf[7] = format; |
| cmdbuf[8] = type; |
| cmdbuf[9] = size; |
| |
| ret = goldfish_gpu_fb_write_pipe(pipe, cmdbuf, sizeof(cmdbuf)); |
| if (ret < 0) |
| { |
| gerr("Write update colorbuffer cmd error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| ret = goldfish_gpu_fb_write_pipe(pipe, pixel, size); |
| if (ret < 0) |
| { |
| gerr("Write update colorbuffer data error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| ret = goldfish_gpu_fb_read_pipe(pipe, &res, sizeof(res)); |
| if (ret < 0) |
| { |
| gerr("Read update colorbuffer result error: %s", strerror(-ret)); |
| return ret; |
| } |
| |
| return res; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_commit |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_commit(FAR struct goldfish_gpu_fb_s *fb, |
| FAR void * buf) |
| { |
| int ret; |
| |
| ret = goldfish_gpu_fb_update_colorbuffer(&fb->pipe, fb->colorbuffer, 0, 0, |
| fb->videoinfo.xres, |
| fb->videoinfo.yres, |
| EGL_RGB565, |
| EGL_UNSIGNED_SHORT_5_6_5, |
| buf, |
| fb->planeinfo.stride |
| * fb->videoinfo.yres); |
| if (ret < 0) |
| { |
| gerr("Failed to update colorbuffer: %d\n", ret); |
| return ret; |
| } |
| |
| ret = goldfish_gpu_fb_post(&fb->pipe, fb->colorbuffer); |
| if (ret < 0) |
| { |
| gerr("Failed to post colorbuffer: %d\n", ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_getvideoinfo |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_getvideoinfo(FAR struct fb_vtable_s *vtable, |
| FAR struct fb_videoinfo_s *vinfo) |
| { |
| FAR struct goldfish_gpu_fb_s *fb = (FAR struct goldfish_gpu_fb_s *)vtable; |
| |
| ginfo("vtable=%p vinfo=%p\n", vtable, vinfo); |
| if (fb && vinfo) |
| { |
| memcpy(vinfo, &fb->videoinfo, sizeof(struct fb_videoinfo_s)); |
| return OK; |
| } |
| |
| gerr("ERROR: Returning EINVAL\n"); |
| return -EINVAL; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_getplaneinfo |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_getplaneinfo(FAR struct fb_vtable_s *vtable, |
| int planeno, |
| FAR struct fb_planeinfo_s *pinfo) |
| { |
| FAR struct goldfish_gpu_fb_s *fb = (FAR struct goldfish_gpu_fb_s *)vtable; |
| |
| ginfo("vtable=%p planeno=%d pinfo=%p\n", vtable, planeno, pinfo); |
| if (fb && planeno == 0 && pinfo) |
| { |
| memcpy(pinfo, &fb->planeinfo, sizeof(struct fb_planeinfo_s)); |
| return OK; |
| } |
| |
| gerr("ERROR: Returning EINVAL\n"); |
| return -EINVAL; |
| } |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_vsync_thread |
| ****************************************************************************/ |
| |
| static int goldfish_gpu_fb_vsync_thread(int argc, FAR char** argv) |
| { |
| FAR struct goldfish_gpu_fb_s *fb = (FAR struct goldfish_gpu_fb_s *) |
| ((uintptr_t)strtoul(argv[1], NULL, 0)); |
| union fb_paninfo_u info; |
| clock_t last = 0; |
| |
| while (1) |
| { |
| clock_t now = clock_systime_ticks(); |
| |
| if (now - last >= MSEC2TICK(16)) |
| { |
| last = now; |
| fb_notify_vsync(&fb->vtable); |
| |
| if (fb_peek_paninfo(&fb->vtable, &info, FB_NO_OVERLAY) == OK) |
| { |
| FAR void *buf = fb->planeinfo.fbmem + |
| fb->planeinfo.stride * |
| info.planeinfo.yoffset; |
| |
| goldfish_gpu_fb_commit(fb, buf); |
| fb_remove_paninfo(&fb->vtable, FB_NO_OVERLAY); |
| } |
| } |
| |
| /* Sleep 8ms, let the idle run */ |
| |
| usleep(8000); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: goldfish_gpu_fb_register |
| ****************************************************************************/ |
| |
| int goldfish_gpu_fb_register(int display) |
| { |
| FAR struct goldfish_gpu_fb_s *fb; |
| FAR char *argv[2]; |
| char arg1[32]; |
| int ret = OK; |
| int pid; |
| |
| fb = kmm_zalloc(sizeof(*fb)); |
| if (fb == NULL) |
| { |
| return -ENOMEM; |
| } |
| |
| /* Initialize the pipe */ |
| |
| ret = goldfish_gpu_fb_init_pipe(&fb->pipe, NULL, |
| "opengles", O_RDWR | O_CLOEXEC); |
| if (ret < 0) |
| { |
| gerr("Failed to initialize pipe: %d\n", ret); |
| goto err_fb_init_failed; |
| } |
| |
| /* Get the framebuffer parameters */ |
| |
| ret = goldfish_gpu_fb_get_param(&fb->pipe, EGL_FB_WIDTH); |
| if (ret < 0) |
| { |
| gerr("Failed to get fb width: %d\n", ret); |
| goto err_fb_get_param_failed; |
| } |
| |
| fb->videoinfo.xres = ret; |
| |
| ret = goldfish_gpu_fb_get_param(&fb->pipe, EGL_FB_HEIGHT); |
| if (ret < 0) |
| { |
| gerr("Failed to get fb height: %d\n", ret); |
| goto err_fb_get_param_failed; |
| } |
| |
| fb->videoinfo.yres = ret; |
| |
| /* Create the colorbuffer */ |
| |
| ret = goldfish_gpu_fb_create_colorbuffer(&fb->pipe, |
| fb->videoinfo.xres, |
| fb->videoinfo.yres, |
| EGL_RGB565); |
| if (ret < 0) |
| { |
| gerr("Failed to create colorbuffer: %d\n", ret); |
| goto err_fb_get_param_failed; |
| } |
| |
| fb->colorbuffer = ret; |
| |
| fb->videoinfo.nplanes = 1; |
| fb->videoinfo.fmt = FB_FMT_RGB16_565; |
| |
| fb->planeinfo.bpp = 16; |
| fb->planeinfo.stride = fb->videoinfo.xres * (fb->planeinfo.bpp >> 3); |
| fb->planeinfo.yres_virtual = fb->videoinfo.yres * 2; |
| fb->planeinfo.xres_virtual = fb->videoinfo.xres; |
| fb->planeinfo.fblen = fb->planeinfo.stride * fb->planeinfo.yres_virtual; |
| fb->planeinfo.fbmem = kmm_zalloc(fb->planeinfo.fblen); |
| |
| if (fb->planeinfo.fbmem == NULL) |
| { |
| gerr("ERROR: Failed to allocate framebuffer memory: %zu KB\n", |
| fb->planeinfo.fblen / 1024); |
| ret = -ENOMEM; |
| goto err_fb_get_param_failed; |
| } |
| |
| fb->vtable.getplaneinfo = goldfish_gpu_fb_getplaneinfo; |
| fb->vtable.getvideoinfo = goldfish_gpu_fb_getvideoinfo; |
| |
| /* Create the vsync thread */ |
| |
| snprintf(arg1, 32, "%p", fb); |
| argv[0] = arg1; |
| argv[1] = NULL; |
| pid = kthread_create("goldfish_gpu_fb_thread", SCHED_PRIORITY_DEFAULT, |
| CONFIG_DEFAULT_TASK_STACKSIZE, |
| goldfish_gpu_fb_vsync_thread, argv); |
| if (pid < 0) |
| { |
| gerr("Failed to create vsync thread: %d\n", pid); |
| goto err_fb_thrad_create_failed; |
| } |
| |
| /* Register the framebuffer */ |
| |
| ret = fb_register_device(display, 0, (FAR struct fb_vtable_s *)fb); |
| if (ret < 0) |
| { |
| goto err_fb_register_failed; |
| } |
| |
| return OK; |
| |
| err_fb_register_failed: |
| kthread_delete(pid); |
| err_fb_thrad_create_failed: |
| kmm_free(fb->planeinfo.fbmem); |
| err_fb_get_param_failed: |
| file_close(&fb->pipe); |
| err_fb_init_failed: |
| kmm_free(fb); |
| return ret; |
| } |