nuttx-apps/examples/fbcon/fbcon_main.c
Justin Hammond a65a5372f9 examples/fbcon: Do not require a builtin registry to spawn the shell.
fbcon asked the registry of built-in applications for the stack size and
priority to spawn its shell with.  A kernel build has no such registry:
its programs are ELF files in a filesystem, which is exactly what the
posix_spawn() below already handles, PATH search and all.  The lookup
therefore fails to compile there.

Ask the registry only where there is one, and take the numbers from this
example's own configuration otherwise.

Tested on an EIC7700 EVB in a kernel build: fbcon renders its console on
a 1080p HDMI framebuffer and spawns a shell whose prompt appears on the
monitor.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
2026-08-09 03:18:28 +08:00

2669 lines
71 KiB
C

/****************************************************************************
* apps/examples/fbcon/fbcon_main.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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 <nuttx/config.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/boardctl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <nuttx/video/fb.h>
#include <nuttx/nx/nxfonts.h>
#include <nuttx/video/fb.h>
#include <ctype.h>
#include <spawn.h>
#include <nuttx/debug.h>
#include <poll.h>
#include <nuttx/ascii.h>
#include <nuttx/lib/builtin.h>
#ifndef CONFIG_NX
# error This application requires NX Graphics
#endif
/* NSH Redirection requires Pipes */
#ifndef CONFIG_DEV_PIPE_SIZE
# error FIFO and Named Pipe Drivers should be enabled in the configuration
#endif
#ifdef CONFIG_NSH_CLE
# warning FBCON console does not support much VT100/EMACS/CLE type functionality yet
#endif
#if !defined(CONFIG_EXAMPLES_FBCON_PIPE_STDOUT) && \
!defined(CONFIG_EXAMPLES_FBCON_PIPE_STDOUT)
# warning FBCON is not configured for either stdout or stderr!
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* stdout and stderr configuration */
#define READ_PIPE 0
#define WRITE_PIPE 1
#define POLL_BUFSIZE 64
#define VT100_MAX_SEQUENCE 6 /* Max length of VT100 sequence to check */
/* VT100 codes that can be decoded */
#define VT100_CLEAREOL {ASCII_ESC, '[', 'K'} /* Clear line from cursor right */
#define VT100_CLEARLINE {ASCII_ESC, '[', '2', 'K'} /* Clear entire line */
#define VT100_CURSOROFF {ASCII_ESC, '[', '?', '2', '5', 'l'} /* Cursor OFF */
#define VT100_CURSORON {ASCII_ESC, '[', '?', '2', '5', 'h'} /* Cursor ON */
#define VT100_CURSORL {ASCII_ESC, '[', '*', 'D'} /* Move cursor left (* = [1...99]) columns */
/* Configuration ************************************************************/
/* Pixel depth. If none provided, pick the smallest enabled pixel depth */
#ifdef CONFIG_EXAMPLES_FBCON_BPP_NX_DEFAULT
# if !defined(CONFIG_NX_DISABLE_1BPP)
# define FBCON_BPP 1
# elif !defined(CONFIG_NX_DISABLE_2BPP)
# define FBCON_BPP 2
# elif !defined(CONFIG_NX_DISABLE_4BPP)
# define FBCON_BPP 4
# elif !defined(CONFIG_NX_DISABLE_8BPP)
# define FBCON_BPP 8
# elif !defined(CONFIG_NX_DISABLE_16BPP)
# define FBCON_BPP 16
# elif !defined(CONFIG_NX_DISABLE_24BPP)
# define FBCON_BPP 24
# elif !defined(CONFIG_NX_DISABLE_32BPP)
# define FBCON_BPP 32
# else
# error "No pixel depth enabled"
# endif
#elif defined(CONFIG_EXAMPLES_FBCON_1BPP)
# define FBCON_BPP 1
#elif defined(CONFIG_EXAMPLES_FBCON_2BPP)
# define FBCON_BPP 2
#elif defined(CONFIG_EXAMPLES_FBCON_4BPP)
# define FBCON_BPP 4
#elif defined(CONFIG_EXAMPLES_FBCON_8BPP)
# define FBCON_BPP 8
#elif defined(CONFIG_EXAMPLES_FBCON_16BPP)
# define FBCON_BPP 16
#elif defined(CONFIG_EXAMPLES_FBCON_24BPP)
# define FBCON_BPP 24
#elif defined(CONFIG_EXAMPLES_FBCON_32BPP)
# define FBCON_BPP 32
#endif
/* Select renderer */
#if (FBCON_BPP == 1)
# define RENDERER nxf_convert_1bpp
#elif (FBCON_BPP == 2)
# define RENDERER nxf_convert_2bpp
#elif (FBCON_BPP == 4)
# define RENDERER nxf_convert_4bpp
#elif (FBCON_BPP == 8)
# define RENDERER nxf_convert_8bpp
#elif (FBCON_BPP == 16)
# define RENDERER nxf_convert_16bpp
#elif (FBCON_BPP == 24)
# define RENDERER nxf_convert_24bpp
#elif (FBCON_BPP == 32)
# define RENDERER nxf_convert_32bpp
#else
# error "Unsupported CONFIG_EXAMPLES_FBCON_BPP"
#endif
/* Background and font color, if defaults chosen */
#ifndef CONFIG_EXAMPLES_FBCON_BGCOLOR
# if (FBCON_BPP == 24) || (FBCON_BPP == 32)
# define FBCON_BGCOLOR 0x00000000
# elif FBCON_BPP == 16
# define FBCON_BGCOLOR 0x0000
# else
# define FBCON_BGCOLOR 0x0
# endif
#else
# define FBCON_BGCOLOR CONFIG_EXAMPLES_FBCON_BGCOLOR
#endif
#ifndef CONFIG_EXAMPLES_FBCON_FCOLOR
# if (FBCON_BPP == 32) || (FBCON_BPP == 24)
# define FBCON_FCOLOR 0xffffff
# elif FBCON_BPP == 16
# define FBCON_FCOLOR 0xffff
# else
# define FBCON_FCOLOR 0xff
# endif
#else
# define FBCON_FCOLOR CONFIG_EXAMPLES_FBCON_FCOLOR
#endif
/* Console font ID */
#ifndef CONFIG_EXAMPLES_FBCON_FONTID
# ifndef NXFONT_DEFAULT
# error NXFONT_DEFAULT not defined
# endif
# define FBCON_FONTID NXFONT_DEFAULT
#else
# define FBCON_FONTID CONFIG_EXAMPLES_FBCON_FONTID
#endif
/* Font glyph caching */
#ifndef CONFIG_EXAMPLES_FBCON_GLCACHE
# define FBCON_GLCACHE 16
#else
# define FBCON_GLCACHE CONFIG_EXAMPLES_FBCON_GLCACHE
#endif
/* Bitmap flags */
#define BMFLAGS_NOGLYPH (1 << 0) /* No glyph available, use space */
#define BM_ISSPACE(bm) (((bm)->flags & BMFLAGS_NOGLYPH) != 0)
/* Sizes and maximums */
#define MAX_USECNT 255 /* Limit to range of a uint8_t */
/* Line spacing. Space (in rows) between lines. */
#define FBCON_LINESPACING CONFIG_EXAMPLES_FBCON_LINESPACING
/* Cursor character */
#define FBCON_CURSORCHAR CONFIG_EXAMPLES_FBCON_CURSORCHAR
/* Spawn task */
#define SPAWN_TASK CONFIG_EXAMPLES_FBCON_SPAWN_TASK
/****************************************************************************
* Private Types
****************************************************************************/
enum exitcode_e
{
FBCON_EXIT_SUCCESS = 0,
FBCON_EXIT_FAIL,
FBCON_EXIT_FD,
FBCON_EXIT_GETVINFO,
FBCON_EXIT_GETPINFO,
FBCON_EXIT_FBMEM,
FBCON_EXIT_FBMEM2,
FBCON_EXIT_FBIO_OVERLAY_INFO,
FBCON_EXIT_FBIO_SELECT_OVERLAY,
FBCON_EXIT_FONTOPEN,
FBCON_EXIT_SETBGCOLOR,
FBCON_EXIT_STDOUT_PIPE_FAILED,
FBCON_EXIT_STDERR_PIPE_FAILED,
FBCON_EXIT_STDIN_PIPE_FAILED,
FBCON_EXIT_LOCAL_STDERR_PIPE_FAILED,
FBCON_EXIT_LOCAL_STDOUT_PIPE_FAILED,
FBCON_EXIT_POSIX_SPAWN_ACTIONS_INIT_FAILED,
FBCON_EXIT_POSIX_SPAWN_FAILED,
FBCON_EXIT_APP_INDEX_UNAVAILABLE,
FBCON_EXIT_APP_POSIX_ATTRIB_INIT_FAILED,
};
/* Describes one cached glyph bitmap */
struct fbcon_glyph_s
{
uint8_t code; /* Character code */
uint8_t height; /* Height of this glyph (in rows) */
uint8_t width; /* Width of this glyph (in pixels) */
uint8_t stride; /* Width of the glyph row (bytes) */
uint8_t usecnt; /* Use count */
FAR uint8_t *bitmap; /* Allocated bitmap memory */
};
/* Describes on character on the display */
struct fbcon_bitmap_s
{
uint8_t code; /* Character code */
uint8_t flags; /* See BMFLAGS_* */
struct nxgl_point_s pos; /* Character position */
};
/* Describes the state of one text display */
struct fbcon_state_s
{
int fd_fb;
FAR void *fbcon_font;
struct fb_videoinfo_s *vinfo;
struct fb_planeinfo_s *pinfo;
#ifdef CONFIG_FB_OVERLAY
struct fb_overlayinfo_s oinfo;
#endif
FAR void *fbmem;
#if 0
/* Revisit needed - no support got dual framebuffers as yet */
FAR void *fbmem2;
FAR void *act_fbmem;
#endif
uint32_t mem2_yoffset;
/* The following describe the console */
nxgl_mxpixel_t bcolor; /* Console background color */
nxgl_mxpixel_t fcolor; /* Console font color */
struct nxgl_size_s wsize; /* Console size */
uint8_t fheight; /* Max height of a font in pixels */
uint8_t fwidth; /* Max width of a font in pixels */
uint8_t spwidth; /* The width of a space */
/* These describe all text already added to the display */
uint8_t maxglyphs; /* Size of the glyph[] array */
uint16_t maxchars; /* Size of the bm[] array */
uint32_t nchars; /* Numb of chars in the bm[] array */
FAR struct fbcon_bitmap_s *bm; /* List of bitmaps on the display */
FAR struct fbcon_glyph_s *glyph; /* Cache of rendered fonts in use */
FAR struct fbcon_bitmap_s cursor; /* Character to use for cursor */
/* VT100 escape sequence processing */
char seq[VT100_MAX_SEQUENCE]; /* Buffered chars */
uint8_t nseq; /* Num buffered chars */
int nwild; /* Num wild collected */
int wildval; /* Wildcard value */
};
typedef void (*seqhandler_t)(FAR struct fbcon_state_s *st);
/* Identifies the state of the VT100 escape sequence processing */
enum fbcon_vt100state_e
{
VT100_NOT_CONSUMED = 0, /* Character is not part of a VT100 escape sequence */
VT100_CONSUMED, /* Character was consumed as part of the VT100 escape processing */
VT100_PROCESSED, /* The full VT100 escape sequence was processed */
VT100_ABORT /* Invalid/unsupported character in buffered escape sequence */
};
struct vt100_sequence_s
{
FAR const char *seq;
seqhandler_t handler;
uint8_t size;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
#if 0
/* Revisit needed = no support for dual framebuffers yet */
static int fb_init_mem2(FAR struct fbcon_state_s *st);
#endif
static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s *pinfo);
static int fbcon_initialize(FAR struct fbcon_state_s *st);
static void fbcon_newline(FAR struct fbcon_state_s *st);
static int fbcon_hidechar(FAR FAR struct fbcon_state_s *st,
FAR const struct fbcon_bitmap_s *bm);
static enum fbcon_vt100state_e fbcon_vt100(FAR struct fbcon_state_s *st,
char ch);
static void fbcon_fillchar(FAR struct fbcon_state_s *st,
FAR const struct nxgl_rect_s *rect,
FAR const struct fbcon_bitmap_s *bm);
static int fbcon_fill(FAR struct fbcon_state_s *st,
FAR struct nxgl_rect_s *rect,
FAR nxgl_mxpixel_t *color);
static void fbcon_fillspace(FAR struct fbcon_state_s *st,
FAR const struct nxgl_rect_s *rect,
FAR const struct fbcon_bitmap_s *bm);
static int fbcon_bitmap(FAR struct fbcon_state_s *st,
FAR const struct nxgl_rect_s *dest,
FAR const uint32_t *src,
unsigned int stride);
static int fbcon_backspace(FAR struct fbcon_state_s *st);
static void fbcon_home(FAR struct fbcon_state_s *st);
static inline void fbcon_movedisplay(FAR struct fbcon_state_s *st,
int bottom, int scrollheight);
static inline void fbcon_scroll(FAR struct fbcon_state_s *st,
int scrollheight);
static void fbcon_freeglyph(FAR struct fbcon_glyph_s *glyph);
static inline FAR struct fbcon_glyph_s *
fbcon_allocglyph(FAR struct fbcon_state_s *st);
static FAR struct fbcon_glyph_s *
fbcon_findglyph(FAR struct fbcon_state_s *st, uint8_t ch);
static inline FAR struct fbcon_glyph_s *
fbcon_renderglyph(FAR struct fbcon_state_s *st,
FAR const struct nx_fontbitmap_s *fbm, uint8_t ch);
static int fbcon_fontsize(FAR void *hfont, uint8_t ch,
FAR struct nxgl_size_s *size);
static FAR struct fbcon_glyph_s *
fbcon_getglyph(FAR struct fbcon_state_s *st, uint8_t ch);
static FAR const struct fbcon_bitmap_s *
fbcon_addchar(FAR struct fbcon_state_s *st, uint8_t ch);
static void fbcon_write(FAR struct fbcon_state_s *st,
FAR char *buffer, size_t buflen);
static bool has_input(int fd);
static void poll_std_streams(FAR struct fbcon_state_s *st);
static void fbcon_putc(FAR struct fbcon_state_s *st, uint8_t ch);
static enum fbcon_vt100state_e fbcon_vt100seq(
FAR struct fbcon_state_s *st, int seqsize);
static FAR const struct vt100_sequence_s *
fbcon_vt100part(FAR struct fbcon_state_s *st, int seqsize);
static enum fbcon_vt100state_e fbcon_vt100(FAR struct fbcon_state_s *st,
char ch);
static void fbcon_erasetoeol(FAR struct fbcon_state_s *st);
static void fbcon_clearline(FAR struct fbcon_state_s *st);
static void fbcon_showcursor(FAR struct fbcon_state_s *st);
static void fbcon_hidecursor(FAR struct fbcon_state_s *st);
static void fbcon_cursorl(FAR struct fbcon_state_s *st);
/****************************************************************************
* Private Data
****************************************************************************/
/* Pipes for NSH Shell: stdin, stdout, stderr */
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN
static int g_nsh_stdin[2];
#endif
static int g_nsh_stdout[2];
static int g_nsh_stderr[2];
/* The VT100 sequences supported by FBCON */
static const char g_erasetoeol[] = VT100_CLEAREOL;
static const char g_clearline[] = VT100_CLEARLINE;
static const char g_cursoroff[] = VT100_CURSOROFF;
static const char g_cursoron[] = VT100_CURSORON;
static const char g_cursorl[] = VT100_CURSORL;
static const struct vt100_sequence_s g_vt100sequences[] =
{
{g_erasetoeol, fbcon_erasetoeol, sizeof(g_erasetoeol)},
{g_clearline, fbcon_clearline, sizeof(g_clearline)},
{g_cursoroff, fbcon_hidecursor, sizeof(g_cursoroff)},
{g_cursoron, fbcon_showcursor, sizeof(g_cursoroff)},
{g_cursorl, fbcon_cursorl, sizeof(g_cursorl)},
{NULL, NULL, 0}
};
#ifdef CONFIG_EXAMPLES_FBCON_SHOW_WELCOME
# ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
static const char g_stdout_hello[] = "Hello FBCON stdout fprintf output!";
# endif
# ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
static const char g_stderr_hello[] = "Hello FBCON stderr fprintf output!";
# endif
#endif
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: fbcon_showcursor
*
* Description:
* Render the cursor character at the current display position.
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_showcursor(FAR struct fbcon_state_s *st)
{
int lineheight;
if ((st->cursor.pos.x + st->fwidth) > st->wsize.w)
{
#ifndef CONFIG_EXAMPLES_FBCON_NOWRAP
/* No.. move to the next line */
fbcon_newline(st);
#else
return OK;
#endif
}
/* Check if we need to scroll up */
lineheight = st->fheight + FBCON_LINESPACING;
while (st->cursor.pos.y >= st->wsize.h + lineheight)
{
fbcon_scroll(st, lineheight);
}
/* Render the cursor glyph onto the display. */
fbcon_fillchar(st, NULL, &st->cursor);
}
/****************************************************************************
* Name: fbcon_hidecursor
*
* Description:
* Remove the cursor character at the current display position.
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_hidecursor(FAR struct fbcon_state_s *st)
{
fbcon_hidechar(st, &st->cursor);
}
/****************************************************************************
* Name: fbcon_cursorl
*
* Description:
* Move the cursor position to the left n characters.
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_cursorl(FAR struct fbcon_state_s *st)
{
int i;
/* Revisit needed. It seems backspace 1 less than calculated.
* This is illogical and yet to be explained and is perhaps related to
* nsh itself when nsh is the spawned app/task.
*/
for (i = 1; i < st->wildval; i++)
{
fbcon_backspace(st);
}
}
/****************************************************************************
* Name: fbcon_erasetoeol
*
* Description:
* Handle the erase-to-eol VT100 escape sequence.
* Erase from and including cursor position
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_erasetoeol(FAR struct fbcon_state_s *st)
{
struct nxgl_rect_s rect;
/* Create a bounding box the size of the remaining iine */
rect.pt1.x = st->cursor.pos.x;
rect.pt2.x = st->wsize.w - 1;
rect.pt1.y = st->cursor.pos.y;
rect.pt2.y = rect.pt1.y + st->fheight + FBCON_LINESPACING - 1;
/* Clear the region */
if (fbcon_fill(st, &rect, &st->bcolor) < 0)
{
gerr("ERROR: fbcon_fill failed: %d\n", errno);
}
/* Because we were clearing from the cursor position, there's no need
* to modify st->nchar as there are no characters after the cursor.
*/
}
/****************************************************************************
* Name: fbcon_clearline
*
* Description:
* Handle the clearline VT100 escape sequence
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* The index of the match in g_vt100sequences[]
*
****************************************************************************/
static void fbcon_clearline(FAR struct fbcon_state_s *st)
{
int i;
FAR struct fbcon_bitmap_s *bm;
struct nxgl_rect_s rect;
/* Create a bounding box the size of the iine */
rect.pt1.x = 0;
rect.pt2.x = st->wsize.w - 1;
rect.pt1.y = st->cursor.pos.y + FBCON_LINESPACING;
rect.pt2.y = st->cursor.pos.y + st->fheight + FBCON_LINESPACING - 1;
/* Clear the region */
if (fbcon_fill(st, &rect, &st->bcolor) < 0)
{
gerr("ERROR: fbcon_fill failed: %d\n", errno);
}
st->cursor.pos.x = st->spwidth;
/* Decrement nchar for each character within in the bounding box */
i = st->nchars;
while (--i > 0)
{
bm = &st->bm[i];
if (bm->pos.y <= rect.pt1.y &&
bm->pos.y + st->fheight >= rect.pt2.y)
{
st->nchars--;
}
}
}
/****************************************************************************
* Name: fbcon_bitmap
*
* Description:
* Copy a rectangular region of a larger image into the rectangle in the
* specified window.
*
* Input Parameters:
* st - pointer to FBCON status structure
* dest - Describes the rectangular region on the display that will
* receive the bit map.
* src - The start of the source image.
* stride - The width of the full source image in pixels.
*
* Returned Value:
* OK on success; ERROR on failure with errno set appropriately
*
****************************************************************************/
static int fbcon_bitmap(FAR struct fbcon_state_s *st,
FAR const struct nxgl_rect_s *dest,
FAR const uint32_t *src,
unsigned int stride)
{
FAR uint32_t *dst;
FAR uint8_t *row;
struct fb_area_s area;
int x;
int y;
area.h = dest->pt2.y - dest->pt1.y + 1;
area.w = dest->pt2.x - dest->pt1.x + 1;
area.x = dest->pt1.x;
area.y = dest->pt1.y;
row = (FAR uint8_t *)st->fbmem + st->pinfo->stride * area.y;
for (y = 0; y < area.h; y++)
{
dst = ((FAR uint32_t *)row) + area.x;
for (x = 0; x < area.w; x++)
{
*dst++ = *(uint32_t *)src++;
}
row += st->pinfo->stride;
}
return OK;
}
/****************************************************************************
* Name: fbcon_movedisplay
*
* Description:
* This function implements the data movement for the scroll operation.
*
* Input Parameters:
* st - pointer to FBCON status structure
* bottom - Start of the vacated area at the bottom to be cleared
* scrollheight - The distance the display must be scrolled
*
* Returned Value:
* OK on success; ERROR on failure with errno set appropriately
*
****************************************************************************/
static inline void fbcon_movedisplay(struct fbcon_state_s *st, int bottom,
int scrollheight)
{
FAR struct fbcon_bitmap_s *bm;
struct nxgl_rect_s rect;
nxgl_coord_t row;
int ret;
int i;
/* Move each row, one at a time. They could all be moved at once but since
* the region is cleared, then re-written, the effect would not be good.
* Below the region is also cleared and re-written,
* however, in much smaller chunks.
*/
rect.pt1.x = 0;
rect.pt2.x = st->wsize.w - 1;
for (row = FBCON_LINESPACING; row < bottom; row += scrollheight)
{
/* Create a bounding box the size of one row of characters */
rect.pt1.y = row;
rect.pt2.y = row + scrollheight - 1;
/* Clear the region */
ret = fbcon_fill(st, &rect, &st->bcolor);
if (ret < 0)
{
gerr("ERROR: fbcon_fill failed: %d\n", errno);
}
/* Fill each character that might lie within in the bounding box */
for (i = 0; i < st->nchars; i++)
{
bm = &st->bm[i];
if (bm->pos.y <= rect.pt1.y &&
bm->pos.y + st->fheight >= rect.pt2.y)
{
fbcon_fillchar(st, &rect, bm);
}
}
}
/* Finally, clear the vacated part of the display */
rect.pt1.y = bottom;
rect.pt2.y = st->wsize.h - 1;
ret = fbcon_fill(st, &rect, &st->bcolor);
if (ret < 0)
{
fprintf(stderr, "fbcon_movedisplay: fbcon_fill failed: %d\n", errno);
}
}
/****************************************************************************
* Name: fbcon_scroll
*
* Description:
* Scroll the display up by a certain amount
*
* Input Parameters:
* st - pointer to FBCON status structure
* scrollheight - The distance the display must be scrolled
*
* Returned Value:
* OK on success; ERROR on failure with errno set appropriately
*
****************************************************************************/
static inline void fbcon_scroll(struct fbcon_state_s *st, int scrollheight)
{
int i;
int j;
/* Adjust the vertical position of each character */
for (i = 0; i < st->nchars; )
{
FAR struct fbcon_bitmap_s *bm = &st->bm[i];
/* Has any part of this character scrolled off the screen? */
if (bm->pos.y < scrollheight + FBCON_LINESPACING)
{
/* Yes... Delete the character by moving all of the data */
for (j = i; j < st->nchars - 1; j++)
{
memcpy(&st->bm[j], &st->bm[j + 1],
sizeof(struct fbcon_bitmap_s));
}
/* Decrement the number of cached characters ('i' is not
* incremented in this case because it already points to the next
* character)
*/
st->nchars--;
}
/* No.. just decrement its vertical position (moving it "up" the
* display by one line).
*/
else
{
bm->pos.y -= scrollheight;
/* We are keeping this one so increment to the next character */
i++;
}
}
/* And move the next display position up by one line as well */
st->cursor.pos.y -= scrollheight;
/* Move the display in the range of 0-height up one scrollheight. */
fbcon_movedisplay(st, st->cursor.pos.y, scrollheight);
}
/****************************************************************************
* Name: fbcon_freeglyph
*
* Description:
* Clear the specified glyph structure
*
* Input Parameters:
* glyph - pointer to glyph structure to be freed
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_freeglyph(FAR struct fbcon_glyph_s *glyph)
{
if (glyph->bitmap)
{
free(glyph->bitmap);
}
memset(glyph, 0, sizeof(struct fbcon_glyph_s));
}
/****************************************************************************
* Name: fbcon_allocglyph
*
* Description:
* Allocate a glyph structure
*
* Input Parameters:
* glyph - pointer to glyph structure to be allocated
*
* Returned Value:
* None
*
****************************************************************************/
static inline FAR struct fbcon_glyph_s *
fbcon_allocglyph(FAR struct fbcon_state_s *st)
{
FAR struct fbcon_glyph_s *glyph = NULL;
FAR struct fbcon_glyph_s *luglyph = NULL;
uint8_t luusecnt;
int i;
/* Search through the glyph cache looking for an unused glyph. Also, keep
* track of the least used glyph as well. We need that if we have to
* replace a glyph in the cache.
*/
for (i = 0; i < st->maxglyphs; i++)
{
/* Is this glyph in use? */
glyph = &st->glyph[i];
if (!glyph->usecnt)
{
/* No.. return this glyph with a use count of one */
glyph->usecnt = 1;
return glyph;
}
/* Yes.. check for the least recently used */
if (!luglyph || glyph->usecnt < luglyph->usecnt)
{
luglyph = glyph;
}
}
/* If we get here, the glyph cache is full. We replace the least used
* glyph with the one we need now. (luglyph can't be NULL).
*/
luusecnt = luglyph->usecnt;
fbcon_freeglyph(luglyph);
/* But lets decrement all of the usecnts so that the new one one be so
* far behind in the counts as the older ones.
*/
if (luusecnt > 1)
{
uint8_t decr = luusecnt - 1;
for (i = 0; i < st->maxglyphs; i++)
{
/* Is this glyph in use? */
glyph = &st->glyph[i];
if (glyph->usecnt > decr)
{
glyph->usecnt -= decr;
}
}
}
/* Then return the least used glyph */
luglyph->usecnt = 1;
return luglyph;
}
/****************************************************************************
* Name: fbcon_findglyph
*
* Description:
* Try and find a glyph in the cache for a given character
*
* Input Parameters:
* st - pointer to FBCON status structure
* ch - the character
*
* Returned Value:
* a pointer to the glyph structure if found or NULL if not
*
****************************************************************************/
static FAR struct fbcon_glyph_s *
fbcon_findglyph(FAR struct fbcon_state_s *st, uint8_t ch)
{
int i;
/* Try to find the glyph in the cache of pre-rendered glyphs */
for (i = 0; i < st->maxglyphs; i++)
{
FAR struct fbcon_glyph_s *glyph = &st->glyph[i];
if (glyph->usecnt > 0 && glyph->code == ch)
{
/* Increment the use count (unless it is already at the max) */
if (glyph->usecnt < MAX_USECNT)
{
glyph->usecnt++;
}
/* And return the glyph that we found */
return glyph;
}
}
return NULL;
}
/****************************************************************************
* Name: fbcon_renderglyph
*
* Description:
* Render a character as a glyph based on the font bitmap and metrics
*
* Input Parameters:
* st - pointer to FBCON status structure
* fbm - pointer to the character bitmap/metrics
* ch - the character to be rendered
*
* Returned Value:
* a pointer to the rendered glyph structure
*
****************************************************************************/
static inline FAR struct fbcon_glyph_s *
fbcon_renderglyph(FAR struct fbcon_state_s *st,
FAR const struct nx_fontbitmap_s *fbm, uint8_t ch)
{
FAR struct fbcon_glyph_s *glyph = NULL;
FAR nxgl_mxpixel_t *ptr;
#if FBCON_BPP < 8
nxgl_mxpixel_t pixel;
#endif
int bmsize;
int row;
int col;
int ret;
/* Make sure that there is room for another glyph */
ginfo("ch=%c [%02x]\n", isprint(ch) ? ch : '.', ch);
/* Allocate the glyph (always succeeds) */
glyph = fbcon_allocglyph(st);
glyph->code = ch;
/* Get the dimensions of the glyph */
glyph->width = fbm->metric.width + fbm->metric.xoffset;
glyph->height = fbm->metric.height + fbm->metric.yoffset;
/* Allocate memory to hold the glyph with its offsets */
glyph->stride = (glyph->width * FBCON_BPP + 7) / 8;
bmsize = glyph->stride * glyph->height;
glyph->bitmap = (FAR uint8_t *)malloc(bmsize);
if (glyph->bitmap)
{
/* Initialize the glyph memory to the background color */
#if FBCON_BPP < 8
pixel = st->bcolor;
# if FBCON_BPP == 1
/* Pack 1-bit pixels into a 2-bits */
pixel &= 0x01;
pixel = (pixel) << 1 | pixel;
# endif
# if FBCON_BPP < 4
/* Pack 2-bit pixels into a nibble */
pixel &= 0x03;
pixel = (pixel) << 2 | pixel;
# endif
/* Pack 4-bit nibbles into a byte */
pixel &= 0x0f;
pixel = (pixel) << 4 | pixel;
ptr = (FAR nxgl_mxpixel_t *)glyph->bitmap;
for (row = 0; row < glyph->height; row++)
{
for (col = 0; col < glyph->stride; col++)
{
/* Transfer the packed bytes into the buffer */
*ptr++ = pixel;
}
}
#elif FBCON_BPP == 24
# error "Additional logic is needed here for 24bpp support"
#else /* FBCON_BPP = {8,16,32} */
ptr = (FAR nxgl_mxpixel_t *)glyph->bitmap;
for (row = 0; row < glyph->height; row++)
{
/* Just copy the color value into the glyph memory */
for (col = 0; col < glyph->width; col++)
{
*ptr++ = st->bcolor;
}
}
#endif
/* Then render the glyph into the allocated memory */
ret = RENDERER((FAR nxgl_mxpixel_t *)glyph->bitmap,
glyph->height, glyph->width, glyph->stride,
fbm, st->fcolor);
if (ret < 0)
{
/* Actually, the RENDERER never returns a failure */
gerr("ERROR: fbcon_renderglyph: RENDERER failed\n");
fbcon_freeglyph(glyph);
glyph = NULL;
}
}
return glyph;
}
/****************************************************************************
* Name: fbcon_fontsize
*
* Description:
* Get the size of a given character bitmap
*
* Input Parameters:
* font - the font of interest
* ch - the character of interest
* size _ pointer to the structure to return the character size
*
* Returned Value:
* Success or ERROR
*
****************************************************************************/
static int fbcon_fontsize(FAR void *hfont, uint8_t ch,
FAR struct nxgl_size_s *size)
{
FAR const struct nx_fontbitmap_s *fbm;
/* No, it is not cached... Does the code map to a font? */
fbm = nxf_getbitmap(hfont, ch);
if (fbm)
{
/* Yes.. return the font size */
size->w = fbm->metric.width + fbm->metric.xoffset;
size->h = fbm->metric.height + fbm->metric.yoffset;
return OK;
}
return ERROR;
}
/****************************************************************************
* Name: fbcon_getglyph
*
* Description:
* Get rendered glyph data for a given character
*
* Input Parameters:
* st - pointer to FBCON status structure
* ch - the character to be rendered
*
* Returned Value:
* a pointer to the glyph data
*
****************************************************************************/
static FAR struct fbcon_glyph_s *
fbcon_getglyph(FAR struct fbcon_state_s *st, uint8_t ch)
{
FAR struct fbcon_glyph_s *glyph;
FAR const struct nx_fontbitmap_s *fbm;
/* First, try to find the glyph in the cache of pre-rendered glyphs */
glyph = fbcon_findglyph(st, ch);
if (!glyph)
{
/* No, it is not cached... Does the code map to a font? */
fbm = nxf_getbitmap(st->fbcon_font, ch);
if (fbm)
{
/* Yes.. render the glyph */
glyph = fbcon_renderglyph(st, fbm, ch);
}
}
return glyph;
}
/****************************************************************************
* Name: fbcon_hidechar
*
* Description:
* Erase a character from the display.
*
* Input Parameters:
* st - pointer to FBCON status structure
* bm - pointer to the character bitmap to be erased
*
* Returned Value:
* Success or error value
*
****************************************************************************/
static int fbcon_hidechar(FAR struct fbcon_state_s *st,
FAR const struct fbcon_bitmap_s *bm)
{
struct nxgl_rect_s bounds;
struct nxgl_size_s fsize;
int ret;
/* Get the size of the font glyph. If fbcon_fontsize, then the
* character will have been rendered as a space, and no display
* modification is required (not an error).
*/
ret = fbcon_fontsize(st->fbcon_font, bm->code, &fsize);
if (ret < 0)
{
/* It was rendered as a space. */
return OK;
}
/* Construct a bounding box for the glyph */
bounds.pt1.x = bm->pos.x;
bounds.pt1.y = bm->pos.y;
bounds.pt2.x = bm->pos.x + fsize.w - 1;
bounds.pt2.y = bm->pos.y + fsize.h - 1;
/* Fill the bitmap region with the background color, erasing the
* character from the display.
*/
return fbcon_fill(st, &bounds, &st->bcolor);
}
/****************************************************************************
* Name: fbcon_addchar
*
* Description:
* Find or create a bitmap structurefor a character
*
* Input Parameters:
* st - pointer to FBCON status structure
* ch - the character to be rendered
*
* Returned Value:
* Pointer to the character bitmap
*
****************************************************************************/
static FAR const struct fbcon_bitmap_s *
fbcon_addchar(FAR struct fbcon_state_s *st,
uint8_t ch)
{
FAR struct fbcon_bitmap_s *bm = NULL;
FAR struct fbcon_glyph_s *glyph;
/* Setup the bitmap information */
bm = &st->bm[st->nchars];
bm->code = ch;
bm->flags = 0;
bm->pos.x = st->cursor.pos.x;
bm->pos.y = st->cursor.pos.y;
/* Find (or create) the matching glyph */
glyph = fbcon_getglyph(st, ch);
if (!glyph)
{
/* No, there is no font for this code. Just mark this as a space. */
bm->flags |= BMFLAGS_NOGLYPH;
/* Set up the next character position */
st->cursor.pos.x += st->spwidth;
}
else
{
/* Set up the next character position */
st->cursor.pos.x += glyph->width;
}
/* Increment nchars to retain this character */
if (st->nchars < st->maxchars)
{
st->nchars++;
}
return bm;
}
/****************************************************************************
* Name: fbcon_fill
*
* Description:
* Fill a region with a colour.
*
* Input Parameters:
* st - pointer to FBCON status structure
* rect - Describes the rectangular region on the display to be filled
* color - The color to fill the rectangle with
*
* Returned Value:
* OK on success; ERROR on failure with errno set appropriately
*
****************************************************************************/
static int fbcon_fill(FAR struct fbcon_state_s *st,
FAR struct nxgl_rect_s *rect,
FAR nxgl_mxpixel_t *color)
{
FAR uint32_t *dest;
FAR uint8_t *row;
struct fb_area_s area;
int x;
int y;
area.h = rect->pt2.y - rect->pt1.y + 1;
area.w = rect->pt2.x - rect->pt1.x + 1;
area.x = rect->pt1.x;
area.y = rect->pt1.y;
row = (FAR uint8_t *)st->fbmem + st->pinfo->stride * area.y;
for (y = 0; y < area.h; y++)
{
dest = (FAR uint32_t *)row + area.x;
for (x = 0; x < area.w; x++)
{
*dest++ = *color;
}
row += st->pinfo->stride;
}
return OK;
}
/****************************************************************************
* Name: fbcon_backspace
*
* Description:
* Remove the last character from the window.
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* OK on success; ERROR on failure with errno set appropriately
*
****************************************************************************/
static int fbcon_backspace(FAR struct fbcon_state_s *st)
{
FAR struct fbcon_bitmap_s *bm;
int ndx;
int ret = -ENOENT;
/* Is there a character on the display? */
if (st->nchars > 0)
{
/* Yes.. Get the index to the last bitmap on the display */
ndx = st->nchars - 1;
bm = &st->bm[ndx];
/* Erase the character from the display */
ret = fbcon_hidechar(st, bm);
/* The current position to the location where the last character was */
st->cursor.pos.x = bm->pos.x;
st->cursor.pos.y = bm->pos.y;
/* Decrement nchars to discard this character */
st->nchars = ndx;
}
return ret;
}
/****************************************************************************
* Name: fbcon_home
*
* Description:
* Set the next character position to the top-left corner of the display.
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_home(FAR struct fbcon_state_s *st)
{
/* The first character is one space from the left */
st->cursor.pos.x = st->spwidth;
/* And FBCON_LINESPACING lines from the top */
st->cursor.pos.y = FBCON_LINESPACING;
/* And reset number of characters in the bm buffer */
st->nchars = 0;
}
/****************************************************************************
* Name: fbcon_newline
*
* Description:
* Set the next character position to the beginning of the next line.
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* None
*
****************************************************************************/
void fbcon_newline(FAR struct fbcon_state_s *st)
{
/* Carriage return: The first character is one space from the left */
st->cursor.pos.x = st->spwidth;
/* Linefeed: Down the max font height + FBCON_LINESPACING */
st->cursor.pos.y += (st->fheight + FBCON_LINESPACING);
}
/****************************************************************************
* Name: fbcon_putc
*
* Description:
* Render the specified character at the current display position.
*
* Input Parameters:
* st - pointer to FBCON status structure
* ch - the character to be rendered
*
* Returned Value:
* None
*
****************************************************************************/
void fbcon_putc(FAR struct fbcon_state_s *st, uint8_t ch)
{
FAR const struct fbcon_bitmap_s *bm;
int lineheight;
/* Ignore carriage returns */
if (ch == '\r')
{
return;
}
/* Handle backspace (treating both BS and DEL as backspace) */
if (ch == ASCII_BS || ch == ASCII_DEL)
{
fbcon_backspace(st);
return;
}
/* Will another character fit on this line? */
if (st->cursor.pos.x + st->fwidth > st->wsize.w)
{
#ifndef CONFIG_EXAMPLES_FBCON_NOWRAP
/* No.. move to the next line */
fbcon_newline(st);
/* If we were about to output a newline character, then don't */
if (ch == ASCII_LF)
{
return;
}
#else
/* No.. Ignore all further characters until a newline is encountered */
if (ch != ASCII_LF)
{
return;
}
#endif
}
/* If it is a newline character, then just perform the logical newline
* operation.
*/
if (ch == ASCII_LF)
{
fbcon_newline(st);
return;
}
/* Check if we need to scroll up */
lineheight = st->fheight + FBCON_LINESPACING;
while (st->cursor.pos.y > st->wsize.h - lineheight)
{
fbcon_scroll(st, lineheight);
}
/* Find the glyph associated with the character and render it
* onto the display.
*/
bm = fbcon_addchar(st, ch);
if (bm)
{
fbcon_fillchar(st, NULL, bm);
}
}
/****************************************************************************
* Name: fbcon_fillspace
*
* Description:
* Handle the special case of a space being displayed
*
* Input Parameters:
* st - pointer to FBCON status structure
* rect- Describes the rectangular region on the display that will
* receive the space.
* bm - pointer to the character bitmap structure with the location
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_fillspace(FAR struct fbcon_state_s *st,
FAR const struct nxgl_rect_s *rect,
FAR const struct fbcon_bitmap_s *bm)
{
struct nxgl_rect_s bounds;
struct nxgl_rect_s intersection;
int ret;
/* Construct a bounding box for the glyph */
bounds.pt1.x = bm->pos.x;
bounds.pt1.y = bm->pos.y;
bounds.pt2.x = bm->pos.x + st->spwidth - 1;
bounds.pt2.y = bm->pos.y + st->fheight - 1;
/* Should this also be clipped to a region in the window? */
if (rect != NULL)
{
/* Get the intersection of the redraw region and the character bitmap */
nxgl_rectintersect(&intersection, rect, &bounds);
}
else
{
/* The intersection is the whole glyph */
nxgl_rectcopy(&intersection, &bounds);
}
/* Check for empty intersections */
if (!nxgl_nullrect(&intersection))
{
/* Fill the bitmap region with the background color, erasing the
* character from the display.
*/
ret = fbcon_fill(st, &intersection, &st->bcolor);
if (ret < 0)
{
gerr("ERROR: fill() method failed: %d\n", ret);
}
}
}
/****************************************************************************
* Name: fbcon_fillchar
*
* Description:
* Implement the character display
*
* Input Parameters:
* st - pointer to FBCON status structure
* rect- Describes the rectangular region on the display that will
* receive the space.
* bm - pointer to the character bitmap structure with the location
*
* Returned Value:
* None
*
****************************************************************************/
void fbcon_fillchar(FAR struct fbcon_state_s *st,
FAR const struct nxgl_rect_s *rect,
FAR const struct fbcon_bitmap_s *bm)
{
FAR struct fbcon_glyph_s *glyph;
struct nxgl_rect_s bounds;
struct nxgl_rect_s intersection;
struct nxgl_size_s fsize;
int ret;
/* Handle the special case of spaces which have no glyph bitmap */
if (BM_ISSPACE(bm))
{
fbcon_fillspace(st, rect, bm);
return;
}
/* Get the size of the font glyph (which may not have been created yet) */
ret = fbcon_fontsize(st->fbcon_font, bm->code, &fsize);
if (ret < 0)
{
/* This would mean that there is no bitmap for the character code and
* that the font would be rendered as a space. But this case should
* never happen here because the BM_ISSPACE() should have already
* found all such cases.
*/
return;
}
/* Construct a bounding box for the glyph */
bounds.pt1.x = bm->pos.x;
bounds.pt1.y = bm->pos.y;
bounds.pt2.x = bm->pos.x + fsize.w - 1;
bounds.pt2.y = bm->pos.y + fsize.h - 1;
/* Should this also be clipped to a region in the window? */
if (rect != NULL)
{
/* Get the intersection of the redraw region and the character bitmap */
nxgl_rectintersect(&intersection, rect, &bounds);
}
else
{
/* The intersection is the whole glyph */
nxgl_rectcopy(&intersection, &bounds);
}
/* Check for empty intersections */
if (!nxgl_nullrect(&intersection))
{
FAR const void *src;
/* Find (or create) the glyph that goes with this font */
glyph = fbcon_getglyph(st, bm->code);
if (!glyph)
{
/* Shouldn't happen */
return;
}
/* Blit the font bitmap into the window */
src = (FAR const void *)glyph->bitmap;
ret = fbcon_bitmap(st, &intersection, src,
(unsigned int)glyph->stride);
if (ret < 0)
{
gerr("ERROR: fbcon_fillchar: fbcon_bitmapwindow failed: %d\n",
ret);
}
}
}
/****************************************************************************
* Name: fbcon_vt100part
*
* Description:
* Return the next entry that is a partial match to the sequence.
*
* Input Parameters:
* st - Driver data structure
* seqsize - The number of bytes in the sequence
*
* Returned Value:
* A pointer to the matching sequence in g_vt100sequences[]
*
****************************************************************************/
FAR const struct vt100_sequence_s *
fbcon_vt100part(FAR struct fbcon_state_s *st, int seqsize)
{
FAR static const struct vt100_sequence_s *seq;
static int ndx;
static int i;
static bool aborted;
static int numw;
static bool collecting;
/* Search from the beginning of the sequence table */
for (ndx = 0; g_vt100sequences[ndx].seq; ndx++)
{
seq = &g_vt100sequences[ndx];
/* Is this sequence big enough? */
aborted = false;
numw = 0;
st->nwild = 0;
/* compare characters received to those in the decodable sequences */
collecting = false;
for (i = 0; i < seqsize; i++)
{
if (seq->seq[i] == '*')
{
/* This sequence has a wildcard */
if ((st->seq[i] < ASCII_0) || (st->seq[i] > ASCII_9))
{
aborted = true;
break;
}
if (++numw > 1)
{
/* This logic only allows collection of one wildcard
* per VT100 sequence
*/
aborted = true;
break;
}
collecting = true;
st->wildval = st->seq[i] - ASCII_0; /* convert from ASCII */
}
else if (collecting)
{
if ((st->seq[i] >= ASCII_0) && (st->seq[i] <= ASCII_9))
{
st->wildval *= 10;
st->wildval += st->seq[i] - ASCII_0;
st->nwild++;
}
else
{
collecting = false;
}
}
else if (st->seq[i] != seq->seq[i])
{
aborted = true;
break;
}
}
if (!aborted)
{
return seq;
}
}
return NULL;
}
/****************************************************************************
* Name: fbcon_vt100seq
*
* Description:
* Determine if the new sequence is a part of a supported VT100 escape
* sequence.
*
* Input Parameters:
* st - Driver data structure
* seqsize - The number of bytes in the sequence
*
* Returned Value:
* state - See enum fbcon_vt100state_e;
*
****************************************************************************/
static enum fbcon_vt100state_e fbcon_vt100seq(
FAR struct fbcon_state_s *st, int seqsize)
{
FAR const struct vt100_sequence_s *seq;
enum fbcon_vt100state_e ret;
/* Is there any VT100 escape sequence that matches what we have
* buffered so far?
*/
seq = fbcon_vt100part(st, seqsize);
if (seq)
{
/* Yes.. if the size of that escape sequence is the same as that
* buffered, then we have an exact match.
*/
if (seqsize >= seq->size + st->nwild)
{
/* Process the VT100 sequence */
seq->handler(st);
st->nseq = 0;
return VT100_PROCESSED;
}
/* The 'seqsize' is still smaller than the potential match(es). We
* will need to collect more characters before we can make a decision.
* Return an indication that we have consumed the character.
*/
return VT100_CONSUMED;
}
/* We get here on a failure. The buffer sequence is not part of any
* supported VT100 escape sequence. If seqsize > 1 then we need to
* return a special value because we have to re-process the buffered
* data.
*/
ret = seqsize > 1 ? VT100_ABORT : VT100_NOT_CONSUMED;
return ret;
}
/****************************************************************************
* Name: fbcon_vt100
*
* Description:
* Test if the newly received byte is part of a VT100 escape sequence
*
* Input Parameters:
* st - Driver data structure
* ch - The newly received character
*
* Returned Value:
* state - See enum fbcon_vt100state_e;
*
****************************************************************************/
static enum fbcon_vt100state_e fbcon_vt100(FAR struct fbcon_state_s *st,
char ch)
{
enum fbcon_vt100state_e ret;
int seqsize;
/* If we have no buffered characters, then 'ch' must be the first character
* of an escape sequence.
*/
if (st->nseq < 1)
{
/* The first character of an escape sequence must be an an escape
* character (!).
*/
if (ch != ASCII_ESC)
{
return VT100_NOT_CONSUMED;
}
/* Add the escape character to the buffer but don't bother with any
* further checking.
*/
st->seq[0] = ASCII_ESC;
st->nseq = 1;
return VT100_CONSUMED;
}
/* Temporarily add the next character to the buffer */
seqsize = st->nseq;
st->seq[seqsize] = ch;
/* Then check if this sequence is part of an a valid escape sequence */
seqsize++;
ret = fbcon_vt100seq(st, seqsize);
if (ret == VT100_CONSUMED)
{
/* The newly added character is indeed part of a VT100 escape sequence
* (which is still incomplete). Keep it in the buffer.
*/
st->nseq = seqsize;
}
return ret;
}
/****************************************************************************
* Name: fbcon_write
*
* Description:
* Put a sequence of characters on the display.
*
* Input Parameters:
* st - pointer to FBCON status structure
* buffer - pointer to the character array to be displayed
* buflen - number of characters to be displayed
*
* Returned Value:
* None
*
****************************************************************************/
static void fbcon_write(FAR struct fbcon_state_s *st,
FAR char *buffer, size_t buflen)
{
char ch;
enum fbcon_vt100state_e state;
ssize_t remaining;
fbcon_hidecursor(st);
for (remaining = buflen; remaining > 0; remaining--)
{
ch = *buffer++;
#if CONFIG_EXAMPLES_FBCON_VT100_DECODE
/* Check if this character is part of a VT100 escape sequence */
do
{
/* Is the character part of a VT100 escape sequence? */
state = fbcon_vt100(st, ch);
switch (state)
{
/* Character is not part of a VT100 escape sequence (and no
* characters are buffer.
*/
default:
case VT100_NOT_CONSUMED:
{
/* We can output the character to the window */
fbcon_putc(st, (uint8_t)ch);
}
break;
/* The full VT100 escape sequence was processed (and the new
* character was consumed)
*/
case VT100_PROCESSED:
/* Character was consumed as part of the VT100 escape processing
* (but the escape sequence is still incomplete.
*/
case VT100_CONSUMED:
{
/* Do nothing... the VT100 logic owns the character */
}
break;
/* Invalid/unsupported character in escape sequence */
case VT100_ABORT:
{
int i;
/* Add the first unhandled character to the window */
fbcon_putc(st, (uint8_t)st->seq[0]);
/* Move all buffer characters down one */
for (i = 1; i < st->nseq; i++)
{
st->seq[i - 1] = st->seq[i];
}
st->nseq--;
/* Then loop again and check if what remains is part of a
* VT100 escape sequence. We could speed this up by
* checking if st->seq[0] == ASCII_ESC.
*/
}
break;
}
}
while (state == VT100_ABORT);
#else
/* Just output the character */
fbcon_putc(st, ch);
#endif /* CONFIG_EXAMPLES_FBCON_VT100_DECODE */
}
fbcon_showcursor(st);
}
/****************************************************************************
* Name: fbdev_get_pinfo
*
* Description:
* Get plane information for a framebuffer
* Note - does not support dual framebuffer memory
*
* Input Parameters:
* fd - file descriptor of the framebuffer
* pinfo - pointer to the3 structure for the plane info
*
* Returned Value:
* OK
*
****************************************************************************/
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;
gerr("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d\n", errcode);
return EXIT_FAILURE;
}
/* Only these pixel depths are supported. viinfo.fmt is ignored, only
* certain color formats are supported.
*/
if (pinfo->bpp != 32 && pinfo->bpp != 24 &&
pinfo->bpp != 16 && pinfo->bpp != 8 &&
pinfo->bpp != 1)
{
gerr("ERROR: bpp=%u not supported\n", pinfo->bpp);
return EXIT_FAILURE;
}
return OK;
}
#if 0
/* Revisit needed = no support for dual framebuffers yet */
/****************************************************************************
* Name: fb_init_mem2
*
* Description:
* Initialise the memory for the second framebuffer
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* OK
*
****************************************************************************/
static int fb_init_mem2(FAR struct fbcon_state_s *st)
{
int ret;
uintptr_t buf_offset;
struct fb_planeinfo_s pinfo;
memset(&pinfo, 0, sizeof(pinfo));
pinfo.display = st->pinfo->display + 1;
if ((ret = fbdev_get_pinfo(st->fd_fb, &pinfo)) < 0)
{
return EXIT_FAILURE;
}
/* Check bpp */
if (pinfo.bpp != st->pinfo->bpp)
{
gerr("ERROR: fbmem2 is incorrect");
return -EINVAL;
}
/* Check the buffer address offset,
* It needs to be divisible by pinfo->stride
*/
buf_offset = pinfo.fbmem - st->fbmem;
if ((buf_offset % st->pinfo->stride) != 0)
{
gerr("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, st->pinfo->stride);
}
/* Calculate the address and yoffset of fbmem2 */
if (buf_offset == 0)
{
/* Use consecutive fbmem2. */
st->mem2_yoffset = st->vinfo->yres;
st->fbmem2 = pinfo.fbmem + st->mem2_yoffset * pinfo.stride;
gerr("ERROR: Use consecutive fbmem2 = %p, yoffset = %" PRIu32"\n",
st->fbmem2, st->mem2_yoffset);
}
else
{
/* Use non-consecutive fbmem2. */
st->mem2_yoffset = buf_offset / st->pinfo->stride;
st->fbmem2 = pinfo.fbmem;
gerr("ERROR: Use non-consecutive fbmem2 = %p, yoffset = %" PRIu32"\n",
st->fbmem2, st->mem2_yoffset);
}
return OK;
}
#endif
/****************************************************************************
* Name: fbcon_initialize
*
* Description:
* Initialise the Framebuffer Console
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* Success or failure code
*
****************************************************************************/
static int fbcon_initialize(FAR struct fbcon_state_s *st)
{
struct nxgl_rect_s rect;
FAR const struct nx_font_s *fontset;
int ret;
/* Get the configured font handles */
st->fbcon_font = nxf_getfonthandle(FBCON_FONTID);
if (!st->fbcon_font)
{
gerr("ERROR: fbcon_main: Failed to get console font handle: %d\n",
errno);
return FBCON_EXIT_FONTOPEN;
}
st->bcolor = FBCON_BGCOLOR;
st->fcolor = FBCON_FCOLOR;
rect.pt1.x = 0;
rect.pt1.y = 0;
rect.pt2.x = st->vinfo->xres - 1;
rect.pt2.y = st->vinfo->yres - 1;
ret = fbcon_fill(st, &rect, &st->bcolor);
if (ret < 0)
{
gerr("ERROR: fbcon_main: fbcon_setbgcolor failed: %d\n", errno);
return FBCON_EXIT_SETBGCOLOR;
}
fontset = nxf_getfontset(st->fbcon_font);
st->fheight = fontset->mxheight;
st->fwidth = fontset->mxwidth;
st->spwidth = fontset->spwidth;
/* we use the entire LCD area for the console */
st->wsize.h = st->vinfo->yres;
st->wsize.w = st->vinfo->xres;
st->cursor.code = FBCON_CURSORCHAR;
if (FBCON_CURSORCHAR != ASCII_SPACE)
{
st->cursor.flags = 0;
}
else
{
st->cursor.flags = BMFLAGS_NOGLYPH;
}
st->nchars = 0;
st->maxglyphs = FBCON_GLCACHE;
/* The first character is one space from the left
* and FBCON_LINESEPARATION lines from the top
*/
st->cursor.pos.x = st->spwidth;
st->cursor.pos.y = FBCON_LINESPACING;
st->maxchars = (st->wsize.w / st->fwidth) *
(st->wsize.h / (st->fheight + FBCON_LINESPACING) - 1);
st->bm = malloc(st->maxchars * sizeof(struct fbcon_bitmap_s));
if (st->bm == NULL)
{
gerr("ERROR: Unable to allocate display buffer memory");
return -ENOMEM;
}
memset(st->bm, 0, st->maxchars * sizeof(struct fbcon_bitmap_s));
st->glyph = malloc(FBCON_GLCACHE * (sizeof(struct fbcon_glyph_s)));
if (st->glyph == NULL)
{
gerr("ERROR: Unable to allocate glyph cache memory");
return -ENOMEM;
}
memset(st->glyph, 0, FBCON_GLCACHE * (sizeof(struct fbcon_glyph_s)));
st->nseq = 0;
return OK;
}
/****************************************************************************
* Name: has_input
*
* Description:
* Return true if a File Descriptor has data to be read.
*
* Input Parameters:
* fd - File Descriptor to be checked
*
* Returned Value:
* True if File Descriptor has data to be read; False otherwise
*
****************************************************************************/
static bool has_input(int fd)
{
int ret;
/* Poll the File Descriptor for input */
struct pollfd fdp;
fdp.fd = fd;
fdp.events = POLLIN;
ret = poll(&fdp, /* File Descriptors */
1, /* Number of File Descriptors */
0); /* Poll Timeout (Milliseconds) */
if (ret > 0)
{
/* If poll is OK and there is input */
if ((fdp.revents & POLLIN) != 0)
{
/* Report that there is input */
return true;
}
/* Else report no input */
return false;
}
else if (ret == 0)
{
/* If timeout, report no input */
return false;
}
else if (ret < 0)
{
/* Handle error */
fprintf(stderr, "poll failed: %d, fd=%d\n", ret, fd);
return false;
}
/* Never comes here */
assert(false);
return false;
}
/****************************************************************************
* Name: poll_std_streams
*
* Description:
* Poll NSH stdout and stderr for output and display the output.
*
* Input Parameters:
* st - pointer to FBCON status structure
*
* Returned Value:
* None
*
****************************************************************************/
static void poll_std_streams(FAR struct fbcon_state_s *st)
{
ssize_t num_ch;
static char buf[POLL_BUFSIZE];
assert(g_nsh_stdout[READ_PIPE] != 0);
assert(g_nsh_stderr[READ_PIPE] != 0);
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN
assert(g_nsh_stdin[READ_PIPE] != 0);
#endif
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
/* Poll NSH stderr to check if there's output to be processed */
if (has_input(g_nsh_stderr[READ_PIPE]))
{
/* Write it to display */
num_ch = read(g_nsh_stderr[READ_PIPE], buf, POLL_BUFSIZE);
if (num_ch > 0)
{
/* display */
fbcon_write(st, buf, num_ch);
}
}
#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDERR */
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
/* Poll NSH stdout to check if there's output to be processed */
if (has_input(g_nsh_stdout[READ_PIPE]))
{
/* Read the output from NSH stdout */
num_ch = read(g_nsh_stdout[READ_PIPE], buf, POLL_BUFSIZE);
if (num_ch > 0)
{
/* Write it to display */
fbcon_write(st, buf, num_ch);
}
}
#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDOUT */
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN
if (has_input(STDIN_FILENO))
{
/* Get and process a character */
ssize_t num = 0;
char ch;
num_ch = read(STDIN_FILENO, &ch, 1);
if (num_ch != 1)
{
fprintf(stderr, "STDIN read failed\n");
return;
}
/* copy it to spawned app/process */
num = write(g_nsh_stdin[WRITE_PIPE], &ch, 1);
if (num != num_ch)
{
fprintf(stderr, "STDIN write failed\n");
return;
}
# if defined(CONFIG_NSH_READLINE)
fbcon_write(st, buf, num_ch);
# elif defined(CONFIG_NSH_CLE)
if (ch == ASCII_LF)
{
fbcon_write(st, &ch, 1);
}
# endif
}
#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDIN */
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: fbcon_main
*
* Description:
* fbcon entry point
*
****************************************************************************/
int main(int argc, FAR char *argv[])
{
FAR struct fbcon_state_s *st;
int index;
int ret;
pid_t pid;
posix_spawn_file_actions_t actions;
posix_spawnattr_t attr;
#ifdef CONFIG_BUILTIN
FAR const struct builtin_s *builtin;
#endif
int exitcode = FBCON_EXIT_SUCCESS;
FAR const char *fbdev = CONFIG_EXAMPLES_FBCON_DEF_FB;
/* There is a single optional argument: The path to the framebuffer
* driver.
*/
if (argc == 2)
{
fbdev = argv[1];
}
else if (argc != 1)
{
fprintf(stderr, "ERROR: Single argument required\n");
fprintf(stderr, "USAGE: %s [<fb-driver-path>]\n", argv[0]);
return FBCON_EXIT_FAIL;
}
st = malloc(sizeof(struct fbcon_state_s));
if (st == NULL)
{
gerr("ERROR: Unable to allocate fbcon_state_s memory");
return -ENOMEM;
}
st->pinfo = malloc(sizeof(struct fb_planeinfo_s));
if (st->pinfo == NULL)
{
gerr("ERROR: Unable to allocate pinfo memory");
return -ENOMEM;
}
st->vinfo = malloc(sizeof(struct fb_videoinfo_s));
if (st->vinfo == NULL)
{
gerr("ERROR: Unable to allocate vinfo memory");
return -ENOMEM;
}
/* Open the framebuffer driver */
st->fd_fb = open(fbdev, O_RDWR);
if (st->fd_fb < 0)
{
int errcode = errno;
gerr("ERROR: Failed to open %s: %d\n", fbdev, errcode);
return FBCON_EXIT_FD;
}
/* Get the characteristics of the framebuffer */
ret = ioctl(st->fd_fb, FBIOGET_VIDEOINFO,
(unsigned long)((uintptr_t)st->vinfo));
if (ret < 0)
{
int errcode = errno;
fprintf(stderr, "ERROR: ioctl(FBIOGET_VIDEOINFO) failed: %d\n",
errcode);
exitcode = FBCON_EXIT_GETVINFO;
goto errout;
}
#ifdef CONFIG_FB_OVERLAY
gerr("ERROR: noverlays: %u\n", st->vinfo->noverlays);
/* Select the first overlay, which should be the composed framebuffer */
ret = ioctl(st->fd_fb, FBIO_SELECT_OVERLAY, 0);
if (ret < 0)
{
int errcode = errno;
gerr("ERROR: ioctl(FBIO_SELECT_OVERLAY) failed: %d\n", errcode);
ret = FBCON_EXIT_FBIO_SELECT_OVERLAY
goto errout;
}
/* Get the first overlay information */
st->oinfo.overlay = 0;
ret = ioctl(st->fd_fb, FBIOGET_OVERLAYINFO,
(unsigned long)((uintptr_t)&st->oinfo));
if (ret < 0)
{
int errcode = errno;
gerr("ERROR: ioctl(FBIOGET_OVERLAYINFO) failed: %d\n", errcode);
ret = FBCON_EXIT_FBIO_OVERLAY_INFO
goto errout;
}
/* select default framebuffer layer */
ret = ioctl(st->fd_fb, FBIO_SELECT_OVERLAY, FB_NO_OVERLAY);
if (ret < 0)
{
int errcode = errno;
gerr("ERROR: ioctl(FBIO_SELECT_OVERLAY) failed: %d\n", errcode);
ret = FBCON_EXIT_FBIO_SELECT_OVERLAY
goto errout;
}
#endif
if ((ret = fbdev_get_pinfo(st->fd_fb, st->pinfo)) < 0)
{
ret = FBCON_EXIT_GETPINFO;
goto errout;
}
/* mmap() the framebuffer.
*
* NOTE: In the FLAT build the frame buffer address returned by the
* FBIOGET_PLANEINFO IOCTL command will be the same as the framebuffer
* address. mmap(), however, is the preferred way to get the framebuffer
* address because in the KERNEL build, it will perform the necessary
* address mapping to make the memory accessible to the application.
*/
st->fbmem = mmap(NULL, st->pinfo->fblen, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FILE, st->fd_fb, 0);
if (st->fbmem == MAP_FAILED)
{
gerr("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d\n", errno);
ret = FBCON_EXIT_FBMEM;
goto errout;
}
#if 0
/* Revisit needed = no support for dual framebuffers yet */
if (st->pinfo->yres_virtual == (st->vinfo->yres * 2))
{
if ((ret = fb_init_mem2(st)) < 0)
{
ret = FBCON_EXIT_FBMEM2;
goto errout;
}
}
#endif
ret = fbcon_initialize(st);
if (ret != OK)
{
exitcode = ret;
goto errout;
}
fbcon_home(st);
fbcon_showcursor(st);
/* Pipe and duplicate STDOUT and/or STDERR */
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
ret = pipe(g_nsh_stdout);
if (ret < 0)
{
_err("stdout pipe failed: %d\n", errno);
return ERROR;
}
close(STDOUT_FILENO);
dup2(g_nsh_stdout[WRITE_PIPE], 1);
#endif
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
ret = pipe(g_nsh_stderr);
if (ret < 0)
{
_err("stderr pipe failed: %d\n", errno);
return ERROR;
}
close(STDERR_FILENO);
dup2(g_nsh_stderr[WRITE_PIPE], 2);
#endif
ret = posix_spawn_file_actions_init(&actions);
if (ret != 0)
{
gerr("ERROR: unable to init spawn sctiond for"
"requested task " "\"""%s""\"" ".\n", SPAWN_TASK);
exitcode = FBCON_EXIT_POSIX_SPAWN_ACTIONS_INIT_FAILED;
goto errout;
}
#ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDIN
/* Spawned app/task may need to intercept and forward STDIN
* otherwise any waiting input characters can be missed if read by the
* spawned app/task first.
*/
ret = pipe(g_nsh_stdin);
if (ret < 0)
{
gerr("ERROR: stdin pipe failed: %d\n", errno);
exitcode = FBCON_EXIT_STDIN_PIPE_FAILED;
goto errout;
}
posix_spawn_file_actions_addclose(&actions, STDIN_FILENO);
posix_spawn_file_actions_adddup2(&actions, g_nsh_stdin[READ_PIPE],
STDIN_FILENO);
#endif /* CONFIG_EXAMPLES_FBCON_PIPE_STDIN */
#ifdef CONFIG_BUILTIN
index = builtin_isavail(SPAWN_TASK);
if (index < 0)
{
gerr("ERROR: requested task " "\"""%s""\"" " not available.\n",
SPAWN_TASK);
exitcode = FBCON_EXIT_APP_INDEX_UNAVAILABLE;
goto errout;
}
builtin = builtin_for_index(index);
if (builtin == NULL)
{
gerr("ERROR: requested task " "\"""%s""\""
" has no scheduling parameters.\n", SPAWN_TASK);
exitcode = FBCON_EXIT_APP_INDEX_UNAVAILABLE;
goto errout;
}
#endif
/* Set up for app/task spawn */
ret = posix_spawnattr_init(&attr);
if (ret != 0)
{
gerr("ERROR: unable to init spawn attributes for"
"requested task " "\"""%s""\"" ".\n", SPAWN_TASK);
exitcode = FBCON_EXIT_APP_POSIX_ATTRIB_INIT_FAILED;
goto errout;
}
#ifdef CONFIG_BUILTIN
attr.stacksize = builtin->stacksize;
attr.priority = builtin->priority;
#else
/* No registry of built-in applications to ask: a kernel build spawns
* programs from the filesystem, and posix_spawn() below finds this one
* on PATH. Its stack and priority come from this example's own
* configuration instead.
*/
attr.stacksize = CONFIG_EXAMPLES_FBCON_STACKSIZE;
attr.priority = CONFIG_EXAMPLES_FBCON_PRIORITY;
#endif
/* Spawn required application */
ret = posix_spawn(&pid, /* Returned Task ID */
SPAWN_TASK, /* Task Path */
&actions, /* Replace STDIN and/or STDOUT and/or STDIN */
&attr, /* Attributes of app/task */
NULL, /* Arguments */
NULL); /* No environment */
if (ret < 0)
{
int errcode = errno;
gerr("ERROR: posix_spawn failed: %d\n", errcode);
exitcode = FBCON_EXIT_POSIX_SPAWN_FAILED;
}
#ifdef CONFIG_EXAMPLES_FBCON_SHOW_WELCOME
# ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDOUT
fprintf(stdout, "%s\n", g_stdout_hello);
fflush(stdout);
# endif
# ifdef CONFIG_EXAMPLES_FBCON_PIPE_STDERR
fprintf(stderr, "%s\n", g_stderr_hello);
fflush(stderr);
# endif
#endif
for (; ; )
{
poll_std_streams(st);
usleep(10000);
}
errout:
close(st->fd_fb);
fprintf(stderr, "FBCON exiting with error %d\n", exitcode);
return exitcode;
}