diff --git a/Documentation/applications/games/nxdoom/index.rst b/Documentation/applications/games/nxdoom/index.rst index 4129f89c7a7..ed9c34a92da 100644 --- a/Documentation/applications/games/nxdoom/index.rst +++ b/Documentation/applications/games/nxdoom/index.rst @@ -93,6 +93,49 @@ To launch the game, just run: and replace the path with your WAD file's path. +Tuning the display for a board +------------------------------ + +DOOM renders a 320x200 image with 8 bits per pixel and a palette, and the port +scales that up and converts it to whatever the frame buffer wants. How much +that costs depends a great deal on the board, so the following options are +available. All of them are disabled by default, which leaves the behaviour +unchanged, and each is worth enabling only where it pays. + +``CONFIG_GAMES_NXDOOM_FB_CMAP`` + Load the DOOM palette into the frame buffer's colour map and blit the + palette indices unconverted, letting the display convert them while it scans + out. This needs a frame buffer that is 8 bits per pixel and supports + ``FBIOPUT_CMAP``, such as an STM32 LTDC layer configured for L8. It removes + the conversion from the blit and halves the amount of data written per + frame. + +``CONFIG_GAMES_NXDOOM_FILLSCREEN`` + Stretch the image over the whole display instead of scaling it by the + largest whole number that fits. This fills a display whose size is not an + exact multiple of 320x200, at the cost of a scale factor that is not uniform + across the image. Without it the image is scaled by a whole number and + centred, leaving a border. + +``CONFIG_GAMES_NXDOOM_ROWSTAGE`` + Build each output row in a staging buffer and copy it out, rather than + writing the frame buffer a pixel at a time, so that the frame buffer only + ever sees burst-friendly copies. Costs a few kilobytes of ``.bss``. This is + worth a lot when the frame buffer is external memory and the data cache is + in write-through mode, and little otherwise. + +``CONFIG_GAMES_NXDOOM_STATIC_SCRNBUF`` + Place the buffer DOOM renders into in ``.bss`` rather than taking it from the + heap, which puts it in internal RAM on a board whose heap is mostly external + memory. The renderer draws in vertical columns, so consecutive writes are + one screen width apart and none of them coalesce, which makes external memory + close to the worst case for it. Costs 64000 bytes of RAM whether the game + runs or not. + +As an illustration of the effect, on an STM32H753 driving a 1024x600 panel +whose frame buffer lives in SDRAM, ``ROWSTAGE`` and ``STATIC_SCRNBUF`` are +worth 2.7 times the frame rate between them. + Other Notes ----------- diff --git a/Documentation/platforms/arm/stm32h7/boards/linum-stm32h753bi/index.rst b/Documentation/platforms/arm/stm32h7/boards/linum-stm32h753bi/index.rst index 3e2c85d0d9c..2f57f072265 100644 --- a/Documentation/platforms/arm/stm32h7/boards/linum-stm32h753bi/index.rst +++ b/Documentation/platforms/arm/stm32h7/boards/linum-stm32h753bi/index.rst @@ -1087,6 +1087,158 @@ that the USB host and the SDMMC peripheral coexist:: nsh> mount -t vfat /dev/mmcsd0 /data nsh> ls /data +nxdoom +------ + +**Purpose:** runs ``NXDoom``, the NuttX port of Chocolate DOOM, on the board's +LCD, played with a USB HID keyboard and reading the game data from the microSD +card. It brings together the LTDC framebuffer, the OTG FS USB host and the +SDMMC peripheral, and uses the external SDRAM for the game's zone memory. + +.. figure:: linum-stm32h753bi-nxdoom.jpg + :figwidth: 60% + :align: center + :alt: DOOM running on the LINUM-STM32H753BI LCD + + DOOM running on the board's 1024x600 LCD + +DOOM renders to a 320x200 8-bit paletted buffer. This configuration stretches +that over the whole 1024x600 panel (``CONFIG_GAMES_NXDOOM_FILLSCREEN``) and +runs the LTDC layer in L8 (``CONFIG_STM32_LTDC_L1_L8`` with +``CONFIG_GAMES_NXDOOM_FB_CMAP``), so the palette indices are written to the +framebuffer unconverted and the display applies the palette from its colour +map as it scans out. That keeps the conversion off the CPU and +halves the amount of data written per frame. + +Two further options matter on this board, both because the framebuffer and the +heap are in external SDRAM: ``CONFIG_GAMES_NXDOOM_ROWSTAGE`` builds each row +in internal RAM so the SDRAM only sees burst copies, and +``CONFIG_GAMES_NXDOOM_STATIC_SCRNBUF`` keeps the buffer DOOM renders into out +of the SDRAM altogether. Between them they are worth 2.7 times the frame rate. + +The game needs about 4 MiB of contiguous memory for its zone. That comes from +the SDRAM region, which is 6 MiB when the LTDC is enabled (the last 2 MiB of +the 8 MiB SDRAM is reserved for the framebuffer). + +**Requirements:** + +* A microSD card, formatted as FAT, holding a DOOM IWAD. The shareware + ``doom1.wad`` and ``freedoom1.wad`` both work, as do the commercial IWADs. +* A USB HID keyboard on the OTG FS service connector. + +**Build and flash:** + +.. code-block:: console + + $ ./tools/configure.sh linum-stm32h753bi:nxdoom + $ make -j + +Flash the resulting ``nuttx.bin`` to the board. + +**How to test:** copy the IWAD to the SD card, plug the card and the keyboard, +reset the board and check that all three devices came up:: + + nsh> ls /dev + /dev: + console + fb0 + kbda + mmcsd0 + null + rtc0 + ttyS0 + zero + +Mount the card and start the game. ``nxdoom`` looks for the IWAD in the +current directory, so either ``cd`` to the mount point first or point it at +the file with ``-iwad``:: + + nsh> mount -t vfat /dev/mmcsd0 /mnt + nsh> nxdoom -iwad /mnt/doom1.wad + NXDoom v0.0.0 + z_init: Init zone memory allocation daemon. + zone memory: Using native C allocator. + Using /mnt/ for configuration and saves + v_init: allocate screens. + m_load_defaults: Load system defaults. + saving config in /mnt/default.cfg + W_Init: Init WADfiles. + adding /mnt/doom1.wad + ========================================================================= + DOOM Shareware + ========================================================================= + NXDoom is free software, covered by the GNU General Public + License. There is NO warranty; not even for MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. You are welcome to change and distribute + copies under certain conditions. See the source for more information. + ========================================================================= + i_init: Setting up machine state. + m_init: Init miscellaneous info. + r_init: Init DOOM refresh daemon - [...................] + p_init: Init Playloop state. + d_check_net_game: Checking network game status. + startskill 2 deathmatch: 0 startmap: 1 startepisode: 1 + player 1 of 1 (1 nodes) + Emulating the behavior of the 'Doom 1.9' executable. + hu_init: Setting up heads up display. + st_init: Init status bar. + +**Copying the IWAD with zmodem:** the configuration also enables the ``rz`` +and ``sz`` commands, so the IWAD can be copied over the serial console +instead of moving the SD card to a card reader. +``CONFIG_SYSTEM_ZMODEM_MOUNTPOINT`` is set to ``/mnt``, so a received file +lands on the SD card:: + + nsh> mount -t vfat /dev/mmcsd0 /mnt + nsh> rz + +and, from the host, with the console closed in any terminal program:: + + $ sz -b --zmodem -w 1024 doom1.wad < /dev/ttyACM0 > /dev/ttyACM0 + +Note that USART1 has no RTS/CTS on this board, so there is no hardware flow +control to throttle the sender. ``CONFIG_USART1_RXBUFSIZE`` is raised to 4096 +to compensate, and the ``-w`` window above makes the host wait for +acknowledgements. Even so this runs at roughly 10 KiB/s, so a 4 MiB IWAD takes +about seven minutes; a card reader is much faster if one is available. + +The game runs on the LCD and is played from the USB keyboard: arrow keys to +move, Ctrl to fire, Shift to run, Alt to strafe, Space to open doors and Esc +for the menu. Configuration and saved games are written to ``/mnt``, so the +card must be mounted read/write. + +.. note:: + The keyboard is registered through the keyboard upper-half driver + (``CONFIG_HIDKBD_KBDUPPER``) rather than as a byte stream. That is what + makes key *release* events available, without which a movement key would + never stop being held down. Note that the byte-stream interface is not + available in this mode, so ``/dev/kbda`` cannot be read as characters while + this configuration is in use. + +.. note:: + ``CONFIG_HIDKBD_NOGETREPORT`` (and the ``CONFIG_USBHOST_ASYNCH`` it needs) + is required. By default the HID keyboard driver asks the keyboard for its + input report over the control pipe with GET_REPORT. Many keyboards accept + that request but always answer with an empty report, and only ever deliver + key data on their interrupt IN endpoint. The symptom is a keyboard that + enumerates as ``/dev/kbda`` and reports no error at all, while no key is + ever seen. With this option the driver reads the interrupt endpoint + instead. + +.. note:: + ``CONFIG_FAT_FORCE_INDIRECT`` is required. Without it the FAT layer reads + whole sectors straight into the caller's buffer, and the SDMMC IDMA cannot + reach the caller's buffer when it lives in external SDRAM. DOOM reads its + lumps into ``malloc()``\ ed memory, and once the internal SRAM regions fill + up those allocations come from SDRAM, so the failure appears part way + through startup rather than immediately:: + + r_init: Init DOOM refresh daemon - [ ]w_read_lump: only read 0 of 5192 on lump 1070 + + Forcing indirect transfers routes every read through the DMA-capable + sector buffer that ``CONFIG_FAT_DMAMEMORY`` allocates, at the cost of one + extra copy per sector. + tone ---- diff --git a/Documentation/platforms/arm/stm32h7/boards/linum-stm32h753bi/linum-stm32h753bi-nxdoom.jpg b/Documentation/platforms/arm/stm32h7/boards/linum-stm32h753bi/linum-stm32h753bi-nxdoom.jpg new file mode 100644 index 00000000000..5145a8ec81b Binary files /dev/null and b/Documentation/platforms/arm/stm32h7/boards/linum-stm32h753bi/linum-stm32h753bi-nxdoom.jpg differ diff --git a/arch/arm/src/stm32h7/stm32_ltdc.c b/arch/arm/src/stm32h7/stm32_ltdc.c index c946c889300..4eeccc47035 100644 --- a/arch/arm/src/stm32h7/stm32_ltdc.c +++ b/arch/arm/src/stm32h7/stm32_ltdc.c @@ -30,6 +30,7 @@ #include +#include #include #include #include @@ -2712,7 +2713,8 @@ static int stm32_setchromakey(struct fb_vtable_s *vtable, # ifdef CONFIG_STM32_FB_CMAP if (oinfo->chromakey >= g_vtable.cmap.len) { - lcderr("ERROR: Clut index %d is out of range\n", oinfo->chromakey); + lcderr("ERROR: Clut index %" PRIu32 " is out of range\n", + oinfo->chromakey); ret = -EINVAL; } else diff --git a/boards/arm/stm32h7/linum-stm32h753bi/configs/nxdoom/defconfig b/boards/arm/stm32h7/linum-stm32h753bi/configs/nxdoom/defconfig new file mode 100644 index 00000000000..b4c2c9f1bba --- /dev/null +++ b/boards/arm/stm32h7/linum-stm32h753bi/configs/nxdoom/defconfig @@ -0,0 +1,105 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_MMCSD_HAVE_WRITEPROTECT is not set +# CONFIG_MMCSD_MMCSUPPORT is not set +# CONFIG_STANDARD_SERIAL is not set +# CONFIG_STM32_LTDC_L1_CHROMAKEYEN is not set +# CONFIG_STM32_LTDC_L2 is not set +CONFIG_ALLOW_GPL_COMPONENTS=y +CONFIG_ARCH="arm" +CONFIG_ARCH_BOARD="linum-stm32h753bi" +CONFIG_ARCH_BOARD_LINUM_STM32H753BI=y +CONFIG_ARCH_CHIP="stm32h7" +CONFIG_ARCH_CHIP_STM32=y +CONFIG_ARCH_CHIP_STM32H753BI=y +CONFIG_ARCH_CHIP_STM32H7=y +CONFIG_ARCH_CHIP_STM32H7_CORTEXM7=y +CONFIG_ARCH_STACKDUMP=y +CONFIG_ARMV7M_DCACHE=y +CONFIG_ARMV7M_DCACHE_WRITETHROUGH=y +CONFIG_ARMV7M_DTCM=y +CONFIG_ARMV7M_ICACHE=y +CONFIG_BOARD_LOOPSPERMSEC=43103 +CONFIG_BUILTIN=y +CONFIG_CRYPTO=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DRIVERS_VIDEO=y +CONFIG_FAT_DMAMEMORY=y +CONFIG_FAT_FORCE_INDIRECT=y +CONFIG_FAT_LCNAMES=y +CONFIG_FAT_LFN=y +CONFIG_FB_OVERLAY=y +CONFIG_FS_FAT=y +CONFIG_FS_PROCFS=y +CONFIG_GAMES_NXDOOM=y +CONFIG_GAMES_NXDOOM_ENDOOM=y +CONFIG_GAMES_NXDOOM_FB_CMAP=y +CONFIG_GAMES_NXDOOM_FILLSCREEN=y +CONFIG_GAMES_NXDOOM_KBDPATH="/dev/kbda" +CONFIG_GAMES_NXDOOM_PREFDIR="/mnt" +CONFIG_GAMES_NXDOOM_ROWSTAGE=y +CONFIG_GAMES_NXDOOM_STACKSIZE=16384 +CONFIG_GAMES_NXDOOM_STATIC_SCRNBUF=y +CONFIG_GRAN=y +CONFIG_GRAN_INTR=y +CONFIG_HIDKBD_KBDUPPER=y +CONFIG_HIDKBD_NOGETREPORT=y +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_INTELHEX_BINARY=y +CONFIG_LIBC_LOCALE=y +CONFIG_LIBM=y +CONFIG_LINE_MAX=64 +CONFIG_MMCSD=y +CONFIG_MMCSD_SDIO=y +CONFIG_MMCSD_SDIOWAIT_WRCOMPLETE=y +CONFIG_MM_REGIONS=5 +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_DISABLE_IFUPDOWN=y +CONFIG_NSH_FILEIOSIZE=512 +CONFIG_NSH_READLINE=y +CONFIG_PREALLOC_TIMERS=4 +CONFIG_RAM_SIZE=245760 +CONFIG_RAM_START=0x20010000 +CONFIG_RAW_BINARY=y +CONFIG_RR_INTERVAL=200 +CONFIG_RTC_ALARM=y +CONFIG_RTC_DATETIME=y +CONFIG_RTC_DRIVER=y +CONFIG_SCHED_HPWORK=y +CONFIG_SCHED_WAITPID=y +CONFIG_SDMMC1_SDIO_MODE=y +CONFIG_START_DAY=6 +CONFIG_START_MONTH=12 +CONFIG_START_YEAR=2011 +CONFIG_STM32_FMC=y +CONFIG_STM32_HSI48=y +CONFIG_STM32_I2C3=y +CONFIG_STM32_LTDC=y +CONFIG_STM32_LTDC_FB_BASE=0xC0600000 +CONFIG_STM32_LTDC_FB_SIZE=2097152 +CONFIG_STM32_OTGFS=y +CONFIG_STM32_PWR=y +CONFIG_STM32_RTC=y +CONFIG_STM32_SDMMC1=y +CONFIG_STM32_USART1=y +CONFIG_SYSTEM_NSH=y +CONFIG_SYSTEM_ZMODEM=y +CONFIG_SYSTEM_ZMODEM_MOUNTPOINT="/mnt" +CONFIG_SYSTEM_ZMODEM_PKTBUFSIZE=1024 +CONFIG_SYSTEM_ZMODEM_RCVBUFSIZE=1024 +CONFIG_SYSTEM_ZMODEM_SNDBUFSIZE=1024 +CONFIG_TASK_NAME_SIZE=0 +CONFIG_USART1_RXBUFSIZE=4096 +CONFIG_USART1_SERIAL_CONSOLE=y +CONFIG_USART1_TXBUFSIZE=1024 +CONFIG_USBHOST=y +CONFIG_USBHOST_ASYNCH=y +CONFIG_USBHOST_HIDKBD=y +CONFIG_USEC_PER_TICK=1000 +CONFIG_VIDEO_FB=y diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index 90b68044e86..26ac25b8370 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -399,6 +399,35 @@ config HIDKBD_ENCODED keys, etc. If this not defined, only 7-bit print-able and control ASCII characters will be provided to the user. +config HIDKBD_KBDUPPER + bool "Register as a keyboard upper-half device" + default n + depends on !HIDKBD_RAWSCANCODES + select INPUT + select INPUT_KEYBOARD + ---help--- + Register /dev/kbd[n] through the keyboard upper-half driver rather + than as a raw character device. Applications then read + struct keyboard_event_s samples instead of a byte stream, which + reports key release events in addition to key press events, and + reports modifiers (Ctrl, Shift, Alt, GUI) as keys of their own. + + This is what an application needs when it must know how long a key + is held down, such as a game. Note that the byte stream interface + is not available at the same time, so this breaks applications that + expect to read characters from /dev/kbd[n]. + +if HIDKBD_KBDUPPER + +config HIDKBD_KBDUPPER_BUFNUMBER + int "Keyboard upper-half buffer size" + default 8 + ---help--- + The number of keyboard events buffered by the upper-half driver + for each opened instance. + +endif # HIDKBD_KBDUPPER + config HIDKBD_ALLSCANCODES bool "Use All Scancodes" default n diff --git a/drivers/usbhost/usbhost_hidkbd.c b/drivers/usbhost/usbhost_hidkbd.c index a52625257b5..c82b411814e 100644 --- a/drivers/usbhost/usbhost_hidkbd.c +++ b/drivers/usbhost/usbhost_hidkbd.c @@ -56,11 +56,16 @@ #include #include -#ifdef CONFIG_HIDKBD_ENCODED +#if defined(CONFIG_HIDKBD_ENCODED) || defined(CONFIG_HIDKBD_KBDUPPER) # include # include #endif +#ifdef CONFIG_HIDKBD_KBDUPPER +# include +# include +#endif + /* Don't compile if prerequisites are not met */ #if defined(CONFIG_USBHOST) && !defined(CONFIG_USBHOST_INT_DISABLE) @@ -133,6 +138,19 @@ #ifdef CONFIG_HIDKBD_RAWSCANCODES # undef CONFIG_HIDKBD_ENCODED +# undef CONFIG_HIDKBD_KBDUPPER +#endif + +/* The table that maps scancodes to special key encodings is shared by the + * encoded byte stream and by the keyboard upper-half interface. + */ + +#if defined(CONFIG_HIDKBD_ENCODED) || defined(CONFIG_HIDKBD_KBDUPPER) +# define HAVE_KBD_ENCODING 1 +#endif + +#ifndef CONFIG_HIDKBD_KBDUPPER_BUFNUMBER +# define CONFIG_HIDKBD_KBDUPPER_BUFNUMBER 8 #endif /* Driver support ***********************************************************/ @@ -238,8 +256,12 @@ struct usbhost_state_s struct work_s rwork; /* For interrupt transfer work */ int16_t nbytes; /* # of bytes actually transferred */ #endif -#ifndef CONFIG_HIDKBD_NODEBOUNCE - uint8_t lastkey[6]; /* For debouncing */ +#if !defined(CONFIG_HIDKBD_NODEBOUNCE) || defined(CONFIG_HIDKBD_KBDUPPER) + uint8_t lastkey[6]; /* Keys down in the previous report */ +#endif +#ifdef CONFIG_HIDKBD_KBDUPPER + struct keyboard_lowerhalf_s lower; /* Keyboard upper-half interface */ + uint8_t lastmod; /* Modifiers held in previous report */ #endif }; @@ -272,8 +294,10 @@ static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, /* Keyboard polling thread */ static void usbhost_destroy(FAR void *arg); +#ifndef CONFIG_HIDKBD_KBDUPPER static void usbhost_putbuffer(FAR struct usbhost_state_s *priv, uint8_t keycode); +#endif #ifdef CONFIG_HIDKBD_ENCODED static void usbhost_putstream(FAR struct lib_outstream_s *self, int ch); #endif @@ -335,8 +359,17 @@ static int usbhost_connect(FAR struct usbhost_class_s *usbclass, FAR const uint8_t *configdesc, int desclen); static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass); -/* Driver methods. We export the keyboard as a standard character driver */ +/* Driver methods. We export the keyboard either as a standard character + * driver or through the keyboard upper-half driver. + */ +static int usbhost_open_priv(FAR struct usbhost_state_s *priv); +static int usbhost_close_priv(FAR struct usbhost_state_s *priv); + +#ifdef CONFIG_HIDKBD_KBDUPPER +static int usbhost_kbdupper_open(FAR struct keyboard_lowerhalf_s *lower); +static int usbhost_kbdupper_close(FAR struct keyboard_lowerhalf_s *lower); +#else static int usbhost_open(FAR struct file *filep); static int usbhost_close(FAR struct file *filep); static ssize_t usbhost_read(FAR struct file *filep, @@ -345,6 +378,7 @@ static ssize_t usbhost_write(FAR struct file *filep, FAR const char *buffer, size_t len); static int usbhost_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup); +#endif /**************************************************************************** * Private Data @@ -374,6 +408,7 @@ static struct usbhost_registry_s g_hidkbd = &g_hidkbd_id /* id[] */ }; +#ifndef CONFIG_HIDKBD_KBDUPPER static const struct file_operations g_hidkbd_fops = { usbhost_open, /* open */ @@ -386,6 +421,24 @@ static const struct file_operations g_hidkbd_fops = NULL, /* truncate */ usbhost_poll /* poll */ }; +#endif + +#ifdef CONFIG_HIDKBD_KBDUPPER + +/* Maps the bits of the HID report modifier byte onto keycodes, so that a + * modifier can be reported as a key in its own right. Indexed by bit + * number: the modifier byte is defined by the HID specification to hold + * LCtrl, LShift, LAlt, LGUI, RCtrl, RShift, RAlt and RGUI, in that order. + */ + +#define USBHID_NMODIFIERS 8 + +static const uint8_t g_modkeycode[USBHID_NMODIFIERS] = +{ + KEYCODE_LCTRL, KEYCODE_LSHIFT, KEYCODE_LALT, KEYCODE_LGUI, + KEYCODE_RCTRL, KEYCODE_RSHIFT, KEYCODE_RALT, KEYCODE_RGUI +}; +#endif /* This is a bitmap that is used to allocate device names /dev/kbda-z. */ @@ -403,7 +456,7 @@ static bool g_caps_lock = false; */ #ifndef CONFIG_HIDKBD_RAWSCANCODES -#ifdef CONFIG_HIDKBD_ENCODED +#ifdef HAVE_KBD_ENCODING /* The first and last scancode values with encode-able values */ @@ -800,7 +853,11 @@ static void usbhost_destroy(FAR void *arg) uinfo("Unregister driver\n"); usbhost_mkdevname(priv, devname); +#ifdef CONFIG_HIDKBD_KBDUPPER + keyboard_unregister(&priv->lower, devname); +#else unregister_driver(devname); +#endif /* Release the device name used by this connection */ @@ -860,6 +917,7 @@ static void usbhost_destroy(FAR void *arg) * ****************************************************************************/ +#ifndef CONFIG_HIDKBD_KBDUPPER static void usbhost_putbuffer(FAR struct usbhost_state_s *priv, uint8_t keycode) { @@ -899,6 +957,7 @@ static void usbhost_putbuffer(FAR struct usbhost_state_s *priv, priv->headndx = head; } +#endif /* CONFIG_HIDKBD_KBDUPPER */ /**************************************************************************** * Name: usbhost_putstream @@ -1076,6 +1135,161 @@ static inline void usbhost_toggle_capslock(void) spin_unlock_irqrestore(&g_lock, flags); } +/**************************************************************************** + * Name: usbhost_kbdupper_keycode + * + * Description: + * Map a HID scancode onto the keycode space used by the keyboard + * upper-half driver: 7-bit ASCII for printable keys, and the values of + * enum kbd_keycode_e for everything else. + * + * Input Parameters: + * scancode - Scan code to be mapped. + * modifier - Ctrl, Alt, Shift, GUI modifier bits + * + * Returned Value: + * The keycode, or zero if the scancode has no representation. + * + ****************************************************************************/ + +#ifdef CONFIG_HIDKBD_KBDUPPER +static uint32_t usbhost_kbdupper_keycode(uint8_t scancode, uint8_t modifier) +{ + /* Check for a special key first. Otherwise a key such as ENTER would be + * reported as the control character that it also maps to. + */ + + if (scancode >= FIRST_ENCODING && scancode <= LAST_ENCODING) + { + uint8_t encoded = encoding[scancode - FIRST_ENCODING]; + + if (encoded != 0) + { + return encoded; + } + } + + /* Otherwise fall back to the printable ASCII mapping. Note that Ctrl is + * deliberately not folded into a control character here: modifiers are + * reported as keys of their own, so the application still knows that Ctrl + * is down and can see which key it is being held with. + */ + + return usbhost_mapscancode(scancode, modifier); +} + +/**************************************************************************** + * Name: usbhost_kbdupper_report + * + * Description: + * Turn a HID keyboard report into key press and key release events and + * hand them to the keyboard upper-half driver. + * + * The HID specification gives no significance to the order of the + * keycodes in the report array, so the only way to know what changed is + * to compare the report against the previous one. + * + * Input Parameters: + * priv - Driver internal state + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_kbdupper_report(FAR struct usbhost_state_s *priv) +{ + FAR struct usbhid_kbdreport_s *rpt = + (FAR struct usbhid_kbdreport_s *)priv->tbuffer; + uint32_t keycode; + uint8_t changed; + int i; + int j; + + /* Report the modifiers whose state changed since the last report */ + + changed = rpt->modifier ^ priv->lastmod; + for (i = 0; changed != 0 && i < USBHID_NMODIFIERS; i++) + { + if ((changed & (1 << i)) != 0) + { + keyboard_event(&priv->lower, g_modkeycode[i], + (rpt->modifier & (1 << i)) != 0 ? + KEYBOARD_PRESS : KEYBOARD_RELEASE); + } + } + + /* Report the keys that were released: down in the last report, but not + * in this one. + */ + + for (i = 0; i < 6; i++) + { + if (priv->lastkey[i] == USBHID_KBDUSE_NONE) + { + continue; + } + + for (j = 0; j < 6; j++) + { + if (rpt->key[j] == priv->lastkey[i]) + { + break; + } + } + + if (j >= 6) + { + keycode = usbhost_kbdupper_keycode(priv->lastkey[i], + rpt->modifier); + if (keycode != 0) + { + keyboard_event(&priv->lower, keycode, KEYBOARD_RELEASE); + } + } + } + + /* Report the keys that were pressed: down in this report, but not in the + * last one. + */ + + for (i = 0; i < 6; i++) + { + if (rpt->key[i] == USBHID_KBDUSE_NONE) + { + continue; + } + + for (j = 0; j < 6; j++) + { + if (priv->lastkey[j] == rpt->key[i]) + { + break; + } + } + + if (j >= 6) + { + if (rpt->key[i] == USBHID_KBDUSE_CAPSLOCK) + { + usbhost_toggle_capslock(); + } + + keycode = usbhost_kbdupper_keycode(rpt->key[i], rpt->modifier); + if (keycode != 0) + { + keyboard_event(&priv->lower, keycode, KEYBOARD_PRESS); + } + } + } + + /* Remember this report so that the next one can be compared against it */ + + memcpy(priv->lastkey, rpt->key, sizeof(priv->lastkey)); + priv->lastmod = rpt->modifier; +} +#endif /* CONFIG_HIDKBD_KBDUPPER */ + /**************************************************************************** * Name: usbhost_extract_keys * @@ -1095,6 +1309,14 @@ static inline void usbhost_toggle_capslock(void) static int usbhost_extract_keys(FAR struct usbhost_state_s *priv) { +#ifdef CONFIG_HIDKBD_KBDUPPER + /* The upper-half driver does its own buffering and poll notification, so + * there is nothing else to do here. + */ + + usbhost_kbdupper_report(priv); + return OK; +#else struct usbhid_kbdreport_s *rpt = (struct usbhid_kbdreport_s *)priv->tbuffer; uint8_t keycode; @@ -1234,6 +1456,7 @@ static int usbhost_extract_keys(FAR struct usbhost_state_s *priv) nxmutex_unlock(&priv->lock); return 0; +#endif /* CONFIG_HIDKBD_KBDUPPER */ } /**************************************************************************** @@ -1991,7 +2214,20 @@ static inline int usbhost_devinit(FAR struct usbhost_state_s *priv) uinfo("Register driver\n"); usbhost_mkdevname(priv, devname); +#ifdef CONFIG_HIDKBD_KBDUPPER + /* Do not set lower.priv here: keyboard_register() claims that field for + * the upper half. We recover our own state with container_of(). + */ + + priv->lower.open = usbhost_kbdupper_open; + priv->lower.close = usbhost_kbdupper_close; + priv->lower.write = NULL; + + ret = keyboard_register(&priv->lower, devname, + CONFIG_HIDKBD_KBDUPPER_BUFNUMBER); +#else ret = register_driver(devname, &g_hidkbd_fops, 0600, priv); +#endif /* We now have to be concerned about asynchronous modification of crefs * because the driver has been registered. @@ -2468,23 +2704,21 @@ static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass) } /**************************************************************************** - * Name: usbhost_open + * Name: usbhost_open_priv * * Description: - * Standard character driver open method. + * Take a reference on the driver instance. This is the common part of + * opening the device, shared by the character driver and the keyboard + * upper-half interfaces. * ****************************************************************************/ -static int usbhost_open(FAR struct file *filep) +static int usbhost_open_priv(FAR struct usbhost_state_s *priv) { - FAR struct inode *inode; - FAR struct usbhost_state_s *priv; irqstate_t flags; int ret; uinfo("Entry\n"); - inode = filep->f_inode; - priv = inode->i_private; /* Make sure that we have exclusive access to the private data structure */ @@ -2526,23 +2760,22 @@ static int usbhost_open(FAR struct file *filep) } /**************************************************************************** - * Name: usbhost_close + * Name: usbhost_close_priv * * Description: - * Standard character driver close method. + * Drop a reference on the driver instance, destroying it if this was the + * last reference to a disconnected device. This is the common part of + * closing the device, shared by the character driver and the keyboard + * upper-half interfaces. * ****************************************************************************/ -static int usbhost_close(FAR struct file *filep) +static int usbhost_close_priv(FAR struct usbhost_state_s *priv) { - FAR struct inode *inode; - FAR struct usbhost_state_s *priv; irqstate_t flags; int ret; uinfo("Entry\n"); - inode = filep->f_inode; - priv = inode->i_private; /* Decrement the reference count on the driver */ @@ -2621,6 +2854,68 @@ static int usbhost_close(FAR struct file *filep) return OK; } +#ifdef CONFIG_HIDKBD_KBDUPPER + +/**************************************************************************** + * Name: usbhost_kbdupper_open + * + * Description: + * Keyboard upper-half open method. + * + ****************************************************************************/ + +static int usbhost_kbdupper_open(FAR struct keyboard_lowerhalf_s *lower) +{ + /* Note that lower->priv cannot be used to find our state: it belongs to + * the upper half, which overwrites it in keyboard_register(). + */ + + return usbhost_open_priv(container_of(lower, struct usbhost_state_s, + lower)); +} + +/**************************************************************************** + * Name: usbhost_kbdupper_close + * + * Description: + * Keyboard upper-half close method. + * + ****************************************************************************/ + +static int usbhost_kbdupper_close(FAR struct keyboard_lowerhalf_s *lower) +{ + return usbhost_close_priv(container_of(lower, struct usbhost_state_s, + lower)); +} + +#else /* CONFIG_HIDKBD_KBDUPPER */ + +/**************************************************************************** + * Name: usbhost_open + * + * Description: + * Standard character driver open method. + * + ****************************************************************************/ + +static int usbhost_open(FAR struct file *filep) +{ + return usbhost_open_priv(filep->f_inode->i_private); +} + +/**************************************************************************** + * Name: usbhost_close + * + * Description: + * Standard character driver close method. + * + ****************************************************************************/ + +static int usbhost_close(FAR struct file *filep) +{ + return usbhost_close_priv(filep->f_inode->i_private); +} + /**************************************************************************** * Name: usbhost_read * @@ -2851,6 +3146,8 @@ errout: return ret; } +#endif /* CONFIG_HIDKBD_KBDUPPER */ + /**************************************************************************** * Public Functions ****************************************************************************/ diff --git a/include/nuttx/input/kbd_codec.h b/include/nuttx/input/kbd_codec.h index 951e1ee472a..10bc1005d09 100644 --- a/include/nuttx/input/kbd_codec.h +++ b/include/nuttx/input/kbd_codec.h @@ -166,7 +166,24 @@ enum kbd_keycode_e KEYCODE_F21, /* Function key 21 */ KEYCODE_F22, /* Function key 22 */ KEYCODE_F23, /* Function key 23 */ - KEYCODE_F24 /* Function key 24 */ + KEYCODE_F24, /* Function key 24 */ + + /* Modifier keys. Drivers that are able to track modifier state (a USB + * HID keyboard, for example) may report these as key press and key + * release events in their own right. That allows an application to bind + * an action to a modifier, or to know that a modifier is being held down, + * which cannot be expressed by folding the modifier into the character + * that it produces. + */ + + KEYCODE_LCTRL, /* Left Ctrl */ + KEYCODE_RCTRL, /* Right Ctrl */ + KEYCODE_LSHIFT, /* Left Shift */ + KEYCODE_RSHIFT, /* Right Shift */ + KEYCODE_LALT, /* Left Alt */ + KEYCODE_RALT, /* Right Alt */ + KEYCODE_LGUI, /* Left GUI (Windows/Command) */ + KEYCODE_RGUI /* Right GUI (Windows/Command) */ }; #define FIRST_KEYCODE KEYCODE_FWDDEL