From e1afa8696b495778521d2852f08a1e8f4e78d56f Mon Sep 17 00:00:00 2001 From: jianglianfang Date: Sat, 9 May 2026 19:04:02 +0800 Subject: [PATCH 1/2] examples/fb: use virtual offsets for non-consecutive buffers Map the second frame at stride * yres and pan with the logical frame height instead of deriving offsets from mapped addresses. Initialize the state, propagate plane query failures and release the second mapping using its actual length. Signed-off-by: jianglianfang Signed-off-by: rongyichang --- examples/fb/fb_main.c | 93 ++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/examples/fb/fb_main.c b/examples/fb/fb_main.c index 3d237df053a..627be1d005a 100644 --- a/examples/fb/fb_main.c +++ b/examples/fb/fb_main.c @@ -155,6 +155,7 @@ static void pan_display(FAR struct fb_state_s *state) { struct pollfd pfd; int ret; + pfd.fd = state->fd; pfd.events = POLLOUT; @@ -193,6 +194,7 @@ static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s *pinfo) if (ioctl(fd, FBIOGET_PLANEINFO, (unsigned long)((uintptr_t)pinfo)) < 0) { int errcode = errno; + fprintf(stderr, "ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d\n", errcode); return EXIT_FAILURE; @@ -226,72 +228,51 @@ static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s *pinfo) static int fb_init_mem2(FAR struct fb_state_s *state) { - int ret; - uintptr_t buf_offset; struct fb_planeinfo_s pinfo; - FAR void *fbmem; + FAR void *mem2; + size_t framesize; + off_t offset; + int ret; + + /* Get display[1] planeinfo */ memset(&pinfo, 0, sizeof(pinfo)); pinfo.display = state->pinfo.display + 1; if ((ret = fbdev_get_pinfo(state->fd, &pinfo)) < 0) { - return EXIT_FAILURE; + return ret; } - fbmem = mmap(NULL, pinfo.fblen, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_FILE, state->fd, 0); - - if (fbmem == MAP_FAILED) - { - int errcode = errno; - fprintf(stderr, "ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d\n", - errcode); - return EXIT_FAILURE; - } - - /* Check bpp */ - if (pinfo.bpp != state->pinfo.bpp) { - fprintf(stderr, "ERROR: fbmem2 is incorrect\n"); + fprintf(stderr, "ERROR: fbmem2 bpp mismatch\n"); return -EINVAL; } - /* Check the buffer address offset, - * It needs to be divisible by pinfo.stride - */ - - buf_offset = fbmem - state->fbmem; + /* Use virtual offset = fblen = stride * yres */ - if ((buf_offset % state->pinfo.stride) != 0) - { - fprintf(stderr, "ERROR: It is detected that buf_offset(%" PRIuPTR ") " - "and stride(%d) are not divisible, please ensure " - "that the driver handles the address offset by itself.\n", - buf_offset, state->pinfo.stride); - } + framesize = state->vinfo.yres * state->pinfo.stride; + offset = framesize; + state->mem2_yoffset = state->vinfo.yres; - /* Calculate the address and yoffset of fbmem2 */ - - if (buf_offset == 0) + mem2 = mmap(NULL, framesize, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FILE, + state->fd, offset); + if (mem2 == MAP_FAILED) { - /* Use consecutive fbmem2. */ - - state->mem2_yoffset = state->vinfo.yres; - state->fbmem2 = fbmem + state->mem2_yoffset * pinfo.stride; - printf("Use consecutive fbmem2 = %p, yoffset = %" PRIu32"\n", - state->fbmem2, state->mem2_yoffset); + fprintf(stderr, "ERROR: mmap mem2 failed: %d\n", errno); + return -errno; } - else - { - /* Use non-consecutive fbmem2. */ - state->mem2_yoffset = buf_offset / state->pinfo.stride; - state->fbmem2 = fbmem; - printf("Use non-consecutive fbmem2 = %p, yoffset = %" PRIu32"\n", - state->fbmem2, state->mem2_yoffset); - } + state->fbmem2 = mem2; + fprintf(stderr, "Mapped FB mem2: %p, yoffset: %" PRIu32 + ", gap: %td (%s)\n", + state->fbmem2, state->mem2_yoffset, + (char *)mem2 - ((char *)state->fbmem + framesize), + ((char *)mem2 == (char *)state->fbmem + framesize) ? + "consecutive" : "non-consecutive"); return 0; } @@ -505,6 +486,7 @@ static void present_area(FAR struct fb_state_s *state, int yoffset = state->act_fbmem == state->fbmem ? 0 : state->mem2_yoffset; + update.y += yoffset; ret = ioctl(state->fd, FBIO_UPDATE, @@ -512,6 +494,7 @@ static void present_area(FAR struct fb_state_s *state, if (ret < 0) { int errcode = errno; + fprintf(stderr, "ERROR: ioctl(FBIO_UPDATE) failed: %d\n", errcode); } @@ -587,10 +570,12 @@ int main(int argc, FAR char *argv[]) /* Open the framebuffer driver */ + memset(&state, 0, sizeof(state)); state.fd = open(fbdev, O_RDWR); if (state.fd < 0) { int errcode = errno; + fprintf(stderr, "ERROR: Failed to open %s: %d\n", fbdev, errcode); return EXIT_FAILURE; } @@ -602,6 +587,7 @@ int main(int argc, FAR char *argv[]) if (ret < 0) { int errcode = errno; + fprintf(stderr, "ERROR: ioctl(FBIOGET_VIDEOINFO) failed: %d\n", errcode); close(state.fd); @@ -623,6 +609,7 @@ int main(int argc, FAR char *argv[]) if (ret < 0) { int errcode = errno; + fprintf(stderr, "ERROR: ioctl(FBIO_SELECT_OVERLAY) failed: %d\n", errcode); close(state.fd); @@ -637,6 +624,7 @@ int main(int argc, FAR char *argv[]) if (ret < 0) { int errcode = errno; + fprintf(stderr, "ERROR: ioctl(FBIOGET_OVERLAYINFO) failed: %d\n", errcode); close(state.fd); @@ -665,6 +653,7 @@ int main(int argc, FAR char *argv[]) if (ret < 0) { int errcode = errno; + fprintf(stderr, "ERROR: ioctl(FBIO_SELECT_OVERLAY) failed: %d\n", errcode); close(state.fd); @@ -693,13 +682,14 @@ int main(int argc, FAR char *argv[]) if (state.fbmem == MAP_FAILED) { int errcode = errno; + fprintf(stderr, "ERROR: mmap() failed: %d\n", errcode); close(state.fd); return EXIT_FAILURE; } - printf("Mapped FB: %p\n", state.fbmem); + printf("Mapped FB mem: %p\n", state.fbmem); /* double buffer mode */ @@ -707,7 +697,9 @@ int main(int argc, FAR char *argv[]) { if ((ret = fb_init_mem2(&state)) < 0) { - goto out; + munmap(state.fbmem, state.pinfo.fblen); + close(state.fd); + return ret; } } @@ -774,8 +766,9 @@ int main(int argc, FAR char *argv[]) printf("Test finished\n"); ret = EXIT_SUCCESS; -out: + munmap(state.fbmem, state.pinfo.fblen); + munmap(state.fbmem2, state.pinfo.fblen); close(state.fd); return ret; From ee3fda187f24efe75d22fa80c88897e3c1288b94 Mon Sep 17 00:00:00 2001 From: jianglianfang Date: Sat, 9 May 2026 19:07:22 +0800 Subject: [PATCH 2/2] graphics/lvgl: backport virtual framebuffer offset handling Backport the NuttX fbdev offset adaptation to the LVGL 9.2.2 archive. Apply the single-file patch through both Make and CMake, including already extracted sources, without modifying independent Git checkouts. Release mapped buffers on failure, rebinding and display deletion. The patch requires the matching nonconsecutive framebuffer driver. Signed-off-by: jianglianfang Signed-off-by: rongyichang --- ...fbdev-use-virtual-framebuffer-offset.patch | 168 ++++++++++++++++++ graphics/lvgl/CMakeLists.txt | 75 +++++--- graphics/lvgl/Makefile | 25 ++- 3 files changed, 237 insertions(+), 31 deletions(-) create mode 100644 graphics/lvgl/0001-nuttx-fbdev-use-virtual-framebuffer-offset.patch diff --git a/graphics/lvgl/0001-nuttx-fbdev-use-virtual-framebuffer-offset.patch b/graphics/lvgl/0001-nuttx-fbdev-use-virtual-framebuffer-offset.patch new file mode 100644 index 00000000000..7fe10f5b7fa --- /dev/null +++ b/graphics/lvgl/0001-nuttx-fbdev-use-virtual-framebuffer-offset.patch @@ -0,0 +1,168 @@ +From: jianglianfang +Date: Sat, 9 May 2026 19:07:22 +0800 +Subject: [PATCH] nuttx: fbdev: use virtual framebuffer offset + +Minimal backport of e2a03f78735104a582a938281dec7d4fd0f3866b +(Gerrit 7769608, patch set 1) to LVGL v9.2.2. + +Map the second frame separately at stride * yres and use yres for +panning instead of deriving a virtual offset from physical addresses. +Unlike the original change, v9.2.2 needs the second mapping introduced +here. Release both mappings on failure, rebinding and display deletion; +keep the existing off-screen mode. Avoid subtracting unrelated mapped +pointers for diagnostics. + +Signed-off-by: jianglianfang +--- +diff --git a/src/drivers/nuttx/lv_nuttx_fbdev.c b/src/drivers/nuttx/lv_nuttx_fbdev.c +--- a/src/drivers/nuttx/lv_nuttx_fbdev.c ++++ b/src/drivers/nuttx/lv_nuttx_fbdev.c +@@ -54,6 +54,7 @@ + static lv_color_format_t fb_fmt_to_color_format(int fmt); + static int fbdev_get_pinfo(int fd, struct fb_planeinfo_s * pinfo); + static int fbdev_init_mem2(lv_nuttx_fb_t * dsc); ++static void fbdev_release(lv_nuttx_fb_t * dsc); + static void display_refr_timer_cb(lv_timer_t * tmr); + static void display_release_cb(lv_event_t * e); + #if defined(CONFIG_FB_UPDATE) +@@ -96,7 +97,8 @@ + LV_ASSERT(disp && file); + lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp); + +- if(dsc->fd >= 0) close(dsc->fd); ++ lv_display_set_draw_buffers(disp, NULL, NULL); ++ fbdev_release(dsc); + + /* Open the file for reading and writing*/ + +@@ -125,14 +127,16 @@ + + lv_color_format_t color_format = fb_fmt_to_color_format(dsc->vinfo.fmt); + if(color_format == LV_COLOR_FORMAT_UNKNOWN) { ++ ret = -EINVAL; + goto errout; + } + + dsc->mem = mmap(NULL, dsc->pinfo.fblen, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FILE, dsc->fd, 0); + if(dsc->mem == MAP_FAILED) { +- LV_LOG_ERROR("ioctl(FBIOGET_PLANEINFO) failed: %d", errno); ++ LV_LOG_ERROR("mmap mem failed: %d", errno); + ret = -errno; ++ dsc->mem = NULL; + goto errout; + } + +@@ -176,8 +180,7 @@ + return 0; + + errout: +- close(dsc->fd); +- dsc->fd = -1; ++ fbdev_release(dsc); + return ret; + } + +@@ -328,8 +331,9 @@ + + static int fbdev_init_mem2(lv_nuttx_fb_t * dsc) + { +- uintptr_t buf_offset; + struct fb_planeinfo_s pinfo; ++ void * mem2; ++ size_t framesize; + int ret; + + lv_memzero(&pinfo, sizeof(pinfo)); +@@ -349,35 +353,46 @@ + return -EINVAL; + } + +- /* Check the buffer address offset, +- * It needs to be divisible by pinfo.stride +- */ +- +- buf_offset = pinfo.fbmem - dsc->mem; +- +- if((buf_offset % dsc->pinfo.stride) != 0) { +- LV_LOG_WARN("It is detected that buf_offset(%" PRIuPTR ") " +- "and stride(%d) are not divisible, please ensure " +- "that the driver handles the address offset by itself.", +- buf_offset, dsc->pinfo.stride); +- } +- +- /* Calculate the address and yoffset of mem2 */ +- +- if(buf_offset == 0) { +- dsc->mem2_yoffset = dsc->vinfo.yres; +- dsc->mem2 = pinfo.fbmem + dsc->mem2_yoffset * pinfo.stride; +- LV_LOG_USER("Use consecutive mem2 = %p, yoffset = %" LV_PRIu32, +- dsc->mem2, dsc->mem2_yoffset); +- } +- else { +- dsc->mem2_yoffset = buf_offset / dsc->pinfo.stride; +- dsc->mem2 = pinfo.fbmem; +- LV_LOG_USER("Use non-consecutive mem2 = %p, yoffset = %" LV_PRIu32, +- dsc->mem2, dsc->mem2_yoffset); +- } ++ /* The second frame follows the first in the virtual framebuffer. */ ++ framesize = (size_t)dsc->vinfo.yres * dsc->pinfo.stride; ++ mem2 = mmap(NULL, framesize, PROT_READ | PROT_WRITE, ++ MAP_SHARED | MAP_FILE, dsc->fd, framesize); ++ if(mem2 == MAP_FAILED) { ++ int errcode = errno; ++ LV_LOG_ERROR("mmap mem2 failed: %d", errcode); ++ return -errcode; ++ } ++ ++ dsc->mem2 = mem2; ++ dsc->mem2_yoffset = dsc->vinfo.yres; ++ LV_LOG_USER("Mapped mem2 = %p, yoffset = %" LV_PRIu32, ++ dsc->mem2, dsc->mem2_yoffset); + + return 0; ++} ++ ++static void fbdev_release(lv_nuttx_fb_t * dsc) ++{ ++ if(dsc->mem2) { ++ munmap(dsc->mem2, (size_t)dsc->vinfo.yres * dsc->pinfo.stride); ++ dsc->mem2 = NULL; ++ } ++ dsc->mem2_yoffset = 0; ++ ++ if(dsc->mem) { ++ munmap(dsc->mem, dsc->pinfo.fblen); ++ dsc->mem = NULL; ++ } ++ ++ if(dsc->mem_off_screen) { ++ free(dsc->mem_off_screen); ++ dsc->mem_off_screen = NULL; ++ } ++ ++ if(dsc->fd >= 0) { ++ close(dsc->fd); ++ dsc->fd = -1; ++ } + } + + static void display_release_cb(lv_event_t * e) +@@ -388,16 +403,7 @@ + lv_display_set_driver_data(disp, NULL); + lv_display_set_flush_cb(disp, NULL); + +- if(dsc->fd >= 0) { +- close(dsc->fd); +- dsc->fd = -1; +- } +- +- if(dsc->mem_off_screen) { +- /* Free the off-screen buffer */ +- free(dsc->mem_off_screen); +- dsc->mem_off_screen = NULL; +- } ++ fbdev_release(dsc); + + lv_free(dsc); + } diff --git a/graphics/lvgl/CMakeLists.txt b/graphics/lvgl/CMakeLists.txt index 5551920aaa4..018b3992aa7 100644 --- a/graphics/lvgl/CMakeLists.txt +++ b/graphics/lvgl/CMakeLists.txt @@ -31,32 +31,59 @@ if(CONFIG_GRAPHICS_LVGL) "${CONFIG_LVGL_VERSION_MAJOR}.${CONFIG_LVGL_VERSION_MINOR}.${CONFIG_LVGL_VERSION_PATCH}" ) - if(NOT EXISTS ${LVGL_DIR}) - set(LVGL_URL - "https://github.com/lvgl/lvgl/archive/refs/tags/v${LVGL_VERSION}.zip") + set(LVGL_STAMP ${LVGL_DIR}/.nuttx_patched-${LVGL_VERSION}) + set(LVGL_FBDEV_PATCH + ${CMAKE_CURRENT_LIST_DIR}/0001-nuttx-fbdev-use-virtual-framebuffer-offset.patch + ) + + if(NOT EXISTS ${LVGL_DIR}/.git) if("${LVGL_VERSION}" STREQUAL "9.2.2") - set(LVGL_URL_HASH - URL_HASH - SHA256=b3d33f7aad1360f588762208d0f563574392debbc28d9187bda86012909f2203 - ) + set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${LVGL_FBDEV_PATCH}) endif() - FetchContent_Declare( - lvgl_fetch - URL ${LVGL_URL} - ${LVGL_URL_HASH} - DOWNLOAD_DIR - ${CMAKE_CURRENT_LIST_DIR} - SOURCE_DIR - ${CMAKE_CURRENT_LIST_DIR}/lvgl - BINARY_DIR - ${NUTTX_BINARY_DIR}/apps/graphics/lvgl/lvgl - DOWNLOAD_NO_PROGRESS true - TIMEOUT 120) - - FetchContent_GetProperties(lvgl_fetch) - - if(NOT lvgl_fetch_POPULATED) - FetchContent_Populate(lvgl_fetch) + + if(NOT EXISTS ${LVGL_STAMP} + OR ("${LVGL_VERSION}" STREQUAL "9.2.2" AND ${LVGL_FBDEV_PATCH} + IS_NEWER_THAN ${LVGL_STAMP})) + # Reset download stamps so a failed patch retries from a fresh archive. + set(LVGL_SUBBUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/lvgl-subbuild) + file(REMOVE_RECURSE ${LVGL_SUBBUILD_DIR}) + set(LVGL_URL + "https://github.com/lvgl/lvgl/archive/refs/tags/v${LVGL_VERSION}.zip") + set(LVGL_URL_HASH "") + set(LVGL_PATCH_COMMAND "") + if("${LVGL_VERSION}" STREQUAL "9.2.2") + set(LVGL_URL_HASH + URL_HASH + SHA256=b3d33f7aad1360f588762208d0f563574392debbc28d9187bda86012909f2203 + ) + set(LVGL_PATCH_COMMAND patch -s -N -p1 -d ${LVGL_DIR} -i + ${LVGL_FBDEV_PATCH}) + endif() + FetchContent_Declare( + lvgl_fetch + URL ${LVGL_URL} + ${LVGL_URL_HASH} + DOWNLOAD_DIR + ${CMAKE_CURRENT_LIST_DIR} + SOURCE_DIR + ${LVGL_DIR} + BINARY_DIR + ${NUTTX_BINARY_DIR}/apps/graphics/lvgl/lvgl + SUBBUILD_DIR + ${LVGL_SUBBUILD_DIR} + PATCH_COMMAND ${LVGL_PATCH_COMMAND} + DOWNLOAD_NO_PROGRESS true + TIMEOUT 120) + + FetchContent_GetProperties(lvgl_fetch) + + if(NOT lvgl_fetch_POPULATED) + FetchContent_Populate(lvgl_fetch) + endif() + file(TOUCH ${LVGL_STAMP}) endif() endif() diff --git a/graphics/lvgl/Makefile b/graphics/lvgl/Makefile index 6bd8e2d1a85..610e8e439ce 100644 --- a/graphics/lvgl/Makefile +++ b/graphics/lvgl/Makefile @@ -72,8 +72,6 @@ LVGL_UNPACKNAME = lvgl UNPACK ?= unzip -o $(if $(V),,-q) CURL ?= curl -L -O $(if $(V),,-Ss) -LVGL_UNPACKDIR = $(WD)/$(LVGL_UNPACKNAME) - $(LVGL_TARBALL): $(ECHO_BEGIN)"Downloading: $(LVGL_TARBALL)" $(Q) $(CURL) $(CONFIG_GRAPH_LVGL_URL)/$(LVGL_TARBALL) @@ -82,19 +80,32 @@ ifneq ($(LVGL_TARBALL_SHA256),) endif $(ECHO_END) -$(LVGL_UNPACKNAME): $(LVGL_TARBALL) +# This minimal backport targets the default LVGL 9.2.2 archive. + +ifeq ($(LVGL_VERSION),9.2.2) +LVGL_PATCHES = $(WD)/0001-nuttx-fbdev-use-virtual-framebuffer-offset.patch +endif + +LVGL_STAMP = $(LVGL_UNPACKNAME)/.nuttx_patched-$(LVGL_VERSION) + +# Recreate incomplete or outdated archive trees; never modify a git checkout. + +ifeq ($(wildcard $(LVGL_UNPACKNAME)/.git),) +$(LVGL_STAMP): $(LVGL_TARBALL) $(LVGL_PATCHES) $(ECHO_BEGIN)"Unpacking: $(LVGL_TARBALL) -> $(LVGL_UNPACKNAME)" ifneq ($(LVGL_TARBALL_SHA256),) $(Q) $(CHECKHASH) sha256 $(LVGL_TARBALL_SHA256) $(LVGL_TARBALL) endif + $(Q) rm -rf $(LVGL_UNPACKNAME) lvgl-$(LVGL_VERSION) $(Q) $(UNPACK) $(LVGL_TARBALL) $(Q) mv lvgl-$(LVGL_VERSION) $(LVGL_UNPACKNAME) - $(Q) touch $(LVGL_UNPACKNAME) + $(Q) for p in $(LVGL_PATCHES); do \ + patch -s -N -p1 -d $(LVGL_UNPACKNAME) < $$p || exit 1; \ + done + $(Q) touch $@ $(ECHO_END) -# Download and unpack tarball if no git repo found -ifeq ($(wildcard $(LVGL_UNPACKNAME)/.git),) -context:: $(LVGL_UNPACKNAME) +context:: $(LVGL_STAMP) endif include $(APPDIR)/Application.mk