summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--CMakeLists.txt20
-rw-r--r--PLAN.md14
-rw-r--r--hello_world.c14
-rw-r--r--main.c26
-rw-r--r--tusb_callbacks.c141
-rw-r--r--tusb_config.h100
-rw-r--r--tusb_helpers.h969
-rwxr-xr-xtusb_helpers.h.gen88
-rw-r--r--usbkeyboard.c15
10 files changed, 1367 insertions, 22 deletions
diff --git a/.gitignore b/.gitignore
index 84c048a..1d62f49 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
/build/
+/MS-LCID.pdf
+/MS-LCID.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ad5fba9..f229d17 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,11 +7,15 @@ project(sbc_harness)
pico_sdk_init()
-add_executable(hello_world hello_world.c)
-target_link_libraries(hello_world pico_stdlib)
-pico_enable_stdio_usb(hello_world 1)
-pico_enable_stdio_uart(hello_world 0)
-pico_enable_stdio_semihosting(hello_world 0)
-pico_enable_stdio_rtt(hello_world 0)
-pico_add_extra_outputs(hello_world) # create map/bin/hex/uf2 file in addition to ELF.
-pico_set_program_url(hello_world "https://git.lukeshu.com/sbc-harness")
+add_executable(sbc_harness)
+target_sources(sbc_harness PUBLIC
+ main.c
+)
+target_include_directories(sbc_harness PUBLIC ${CMAKE_CURRENT_LIST_DIR}) # So TinyUSB can find tusb_config.h
+target_link_libraries(sbc_harness pico_stdlib)
+pico_enable_stdio_usb(sbc_harness 0)
+pico_enable_stdio_uart(sbc_harness 1)
+pico_enable_stdio_semihosting(sbc_harness 0)
+pico_enable_stdio_rtt(sbc_harness 0)
+pico_add_extra_outputs(sbc_harness) # create map/bin/hex/uf2 file in addition to ELF.
+pico_set_program_url(sbc_harness "https://git.lukeshu.com/sbc-harness")
diff --git a/PLAN.md b/PLAN.md
new file mode 100644
index 0000000..4b83eee
--- /dev/null
+++ b/PLAN.md
@@ -0,0 +1,14 @@
+- with hardware I have:
+ 1. type "hello world" as a USB keyboard
+ 2. get networking up (ping)
+ 3. forward port 21 as a USB keyboard
+ 4. forward a 9p file as a USB keyboard
+ 5. connect UART as a 9p socket
+- waiting on hardware:
+ - sdcard slot:
+ 1. whatever the "hello world" of SD is
+ 2. as a device on 9p
+ - HDMI socket
+ 1. PicoDVI hello-world
+ 2. "PiciDVI" to network
+ 3. reverse the flow of PicoDVI
diff --git a/hello_world.c b/hello_world.c
deleted file mode 100644
index 53e341c..0000000
--- a/hello_world.c
+++ /dev/null
@@ -1,14 +0,0 @@
-#include <stdio.h>
-#include "pico/stdlib.h"
-
-int main() {
- stdio_init_all();
- gpio_init(PICO_DEFAULT_LED_PIN);
- gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
-
- for (bool st = true; true; st = !st) {
- gpio_put(PICO_DEFAULT_LED_PIN, st);
- printf("Hello, world!\n");
- sleep_ms(1000);
- }
-}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..9b508c0
--- /dev/null
+++ b/main.c
@@ -0,0 +1,26 @@
+/* pico-sdk */
+#include <stdio.h>
+#include "pico/stdlib.h"
+
+/* TinyUSB */
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+int main() {
+ /* pico-sdk initialization */
+ stdio_uart_init();
+ gpio_init(PICO_DEFAULT_LED_PIN);
+ gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
+
+ /* TinyUSB initialization */
+ board_init();
+ tud_init(BOARD_TUD_RHPORT);
+ if (board_init_after_tusb)
+ board_init_after_tusb();
+
+ /* Event loop. */
+ for (;;) {
+ tud_task(); /* run TinyUSB */
+ usbkeyboard_task();
+ }
+}
diff --git a/tusb_callbacks.c b/tusb_callbacks.c
new file mode 100644
index 0000000..84214c9
--- /dev/null
+++ b/tusb_callbacks.c
@@ -0,0 +1,141 @@
+/* tinyusb */
+#include "bsp/board_api.h"
+#include "tusb.h"
+
+/* local */
+#include "tusb_helpers.h"
+
+#define NUM_INTERFACES CFG_TUD_HID
+#define NUM_CONFIGS 1
+
+enum {
+ STRID_LANGID = 0,
+ STRID_MANUF,
+ STRID_PRODUCT,
+ STRID_SERIAL,
+
+ STRID_NONE = 0,
+};
+
+/**
+ * Return a pointer to the USB "String Descriptor" (see USB 2.0 §9.6.7
+ * "String").
+ */
+uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
+ static uint16_t desc[0xFF/2];
+ static const max_u16len = (sizeof desc / sizeof desc[0]) - 1;
+
+ static uint16_t const langids[] = {
+ LANGID_EN_US,
+ };
+
+ size_t bytelen = 0;
+ if (index == 0) {
+ memcpy(&desc[1], langids, sizeof langids);
+ bytelen = sizeof langids;
+ } else {
+ switch (langid) {
+ case LANGID_EN_US:
+ switch (index) {
+ case STRID_MANUF: bytelen = 2 * utf16_strncpy(&desc[1], UTF16("Umorpha Systems"), max_u16len); break;
+ case STRID_PRODUCT: bytelen = 2 * utf16_strncpy(&desc[1], UTF16("SBC-Harness Keyboard"), max_u16len); break;
+ case STRID_SERIAL: bytelen = 2 * board_usb_get_serial(&desc[1], max_u16len); break;
+ default:
+ return NULL;
+ };
+ default:
+ return NULL;
+ }
+ }
+ assert(bytelen <= 0xFF-2);
+ desc[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2+bytelen));
+ return desc;
+}
+
+/**
+ * Return a pointer to the USB "Device Descriptor" (see USB 2.0 §9.6.1
+ * "Device") as a byte-array.
+ */
+uint8_t const *tud_decriptor_device_cb(void) {
+ /* Our device descriptor is static, so just declare it as a
+ * static const. */
+ static tusb_desc_device_t const desc = {
+ .bLength = sizeof(tusb_desc_device_t),
+ .bDescriptorType = TUSB_DESC_DEVICE,
+ .bcdUSB = 0x200, /* USB 2.0 */
+
+ /* Use the "base device class", which means to use information
+ * form interface descriptors instead of a global device
+ * descriptor. */
+ .bDeviceClass = 0x00,
+ .bDeviceSubClass = 0x00,
+ .bDeviceProtocol = 0x00,
+
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+
+ .idVendor = 0xCAFE, /* Vendor ID (assigned by the USB-IF) */
+ .idProduct = 0x4001, /* Product ID (assigned by the manufacturer) */
+ .bcdDevice = 0x0100, /* Device release number */
+
+ .iManufacturer = STRID_MANUF /* Index of string descriptor describing manufacturer */
+ .iProduct = STRID_PRODUCT, /* Index of string descriptor describing product */
+ .iSerialNumber = STRID_SERIAL, /* Index of string descriptor describing the device's serial number */
+
+ .bNumConfigurations = NUM_CONFIGS, /* Number of possible configurations */
+ };
+ return (uint8_t const *) &desc;
+}
+
+/**
+ * A USB-HID "Report Descriptor" (see USB-HID 1.11 §6.2.2 "Report
+ * Descriptor") describing a keyboard.
+ */
+static uint8_t const hid_report_descriptor_keyboard[] = { TUD_HID_REPORT_DESC_KEYBOARD() };
+
+/**
+ * Return a pointer to the USB config "Configuration Descriptor" (see
+ * USB 2.0 §9.6.4 "Configuration") for the given index.
+ */
+uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
+ static uint8_t const configs[NUM_CONFIGS][] = {
+ {
+ /* USB configuration descriptor header (USB 2.0 §9.6.4 "Configuration") */
+ TUD_CONFIG_DESCRIPTOR(
+ 1, /* bConfigurationValue ; Value to use as an argument to the SetConfiguration() request to select this configuration */
+ NUM_INTERFACES, /* bNumInterfaces ; Number of interfaces supported by this configuration */
+ STRID_NONE, /* iConfiguration ; Index of string descriptor describing this configuration */
+ TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN, /* wTotalLength ; Total lenggth of data returned for this configuration */
+ TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, /* bmAttributes ; Bitmap of flags (self-powered and remote-wakeup are the only flags defined in USB 2.0) */
+ 100), /* bMaxPower (in mA) ; Maximum power consumption of the device when in this configuration */
+ /* USB-HID input-only descriptor; consisting of 3 parts:
+ * 1. an interface descriptor (USB 2.0 §9.6.5 "Interface"),
+ * 2. a class-specific (class=HID) descriptor of type HID (USB-HID 1.11 §6.2.1 "HID Descriptor"),
+ * 3. an endpoint descriptor for inputs (USB 2.0 §9.6.6 "Endpoint").
+ * The TUD_HID_DESCRIPTOR() macro takes care of this for us. */
+ TUD_HID_DESCRIPTOR(
+ 0, /* interface : bInterfaceNumber ; Number of this interface (0-indexed) */
+ STRID_NONE, /* interface : iInterface ; Index of string descriptor describing this interface */
+ HID_ITF_PROTOCOL_KEYBOARD, /* interface : bInterfaceProtocol ; see USB-HID 1.11 §4.3 "Protocols" */
+ sizeof(hid_report_descriptor_keyboard), /* hid : wDescriptorLength ; Total size of report descriptor */
+ TUD_ENDPOINT_IN | 1, /* endpoint : bEndpointAddress ; Direction | endpoint number (arbitrary?) */
+ CFG_TUD_HID_EP_BUFSIZE, /* endpoint : wMaxPacketSize ; Maximum packet size this endpoint is capable of sending or receiving */
+ 10), /* endpoint : bInterval ; poll interval (in milliseconds?) */
+ },
+ };
+ if (index >= sizeof config / sizeof configs[0])
+ return NULL;
+ return configs[index];
+}
+
+/**
+ * Return a pointer to the HID "Report Descriptor" (see USB-HID 1.11
+ * §6.2.2 "Report Descriptor") for the given index.
+ */
+uint8_t const *tud_hid_descriptor_report_cb(uint8_t index) {
+ static uint8_t const *reports[NUM_INTERFACES] = {
+ hid_report_descriptor_keyboard,
+ };
+ if (index >= sizeof reports / sizeof reports[0])
+ return NULL;
+ return reports[index];
+}
diff --git a/tusb_config.h b/tusb_config.h
new file mode 100644
index 0000000..051c39c
--- /dev/null
+++ b/tusb_config.h
@@ -0,0 +1,100 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ * Copyright (c) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef _TUSB_CONFIG_H_
+#define _TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------
+// TinyUSB Device (TUD) initialization for rp2040-based boards
+//--------------------------------------------------------------------
+
+// Which USB port to use for the RootHub.
+// The rp2040 only has 1 port, so it's gotta be port #0.
+#define BOARD_TUD_RHPORT 0
+
+// RHPort max operational speed.
+// Use OPT_MODE_DEFAULT_SPEED for max speed supported by MCU.
+#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+
+//--------------------------------------------------------------------
+// Configuration: common
+//--------------------------------------------------------------------
+
+// defined by compiler flags
+#ifndef CFG_TUSB_MCU
+#error CFG_TUSB_MCU must be defined
+#endif
+
+#define CFG_TUSB_OS OPT_OS_NONE
+#define CFG_TUSB_DEBUG 0
+
+// USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
+// Tinyusb use follows macros to declare transferring memory so that they can be put
+// into those specific section.
+// e.g
+// - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
+// - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
+#define CFG_TUSB_MEM_SECTION /* blank */
+#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
+
+#define CFG_TUD_ENABLED 1
+#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
+
+//--------------------------------------------------------------------
+// Configuration: TinyUSB Device (TUD)
+//--------------------------------------------------------------------
+
+// control endpoint max packet size (only 8, 16, 32, or 64 are valid)
+#define CFG_TUD_ENDPOINT0_SIZE 64
+
+// Which of TinyUSB's built-in class drivers to enable.
+#define CFG_TUD_CDC 0 // Communications Device Class (e.g. ttyUSB) https://www.usb.org/sites/default/files/CDC1.2_WMC1.1_012011.zip
+#define CFG_TUD_MSC 0 // Mass Storage Class https://www.usb.org/sites/default/files/Mass_Storage_Specification_Overview_v1.4_2-19-2010.pdf
+#define CFG_TUD_HID 1 // Human Interface Device (num is how many interfaces) https://www.usb.org/sites/default/files/hid1_11.pdf
+#define CFG_TUD_AUDIO 0 // Audio https://www.usb.org/sites/default/files/audio10.pdf
+#define CFG_TUD_VIDEO 0 // Video https://www.usb.org/sites/default/files/USB_Video_Class_1_5.zip
+#define CFG_TUD_MIDI 0 // Musical Instrument Digital Interface https://www.usb.org/sites/default/files/USB%20MIDI%20v2_0.pdf
+#define CFG_TUD_VENDOR 0 // ???
+#define CFG_TUD_USBTMC 0 // Test & Measurement Class https://www.usb.org/sites/default/files/USBTMC_1_006a.zip
+#define CFG_TUD_DFU_RUNTIME 0 // Device Firmware Upgrade https://www.usb.org/sites/default/files/DFU_1.1.pdf
+#define CFG_TUD_DFU 0 // Device Firmware Upgrade https://www.usb.org/sites/default/files/DFU_1.1.pdf
+#define CFG_TUD_ECM_RNDIS 0 // net
+#define CFG_TUD_NCM 0 // net
+#define CFG_TUD_BTH 0 // Bluetooth
+
+// HID buffer size Should be sufficient to hold ID (if any) + Data
+#define CFG_TUD_HID_EP_BUFSIZE 8
+
+//--------------------------------------------------------------------
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TUSB_CONFIG_H_ */
diff --git a/tusb_helpers.h b/tusb_helpers.h
new file mode 100644
index 0000000..452a32e
--- /dev/null
+++ b/tusb_helpers.h
@@ -0,0 +1,969 @@
+/* tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB
+ *
+ * Copyright (c) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef _USB_HELPERS_H_
+#define _USB_HELPERS_H_
+
+/* USB 2.0 §9.6.7 "String" says "The list of currently defined USB LANGIDs can be found at
+ * http://www.usb.org/developers/docs.html.", but that page 404s.
+ *
+ * Once upon a time the USB-IF (usb.org) published a "Language Identifiers (LANGIDs)" version 1.0,
+ * dated 2000-03-29. There is no longer any mention of this on usb.org, but I found a copy at
+ * http://www.baiheee.com/Documents/090518/090518112619/USB_LANGIDs.pdf
+ *
+ * So how does the USB-IF defined LANGIDs these days?
+ *
+ * https://www.usb.org/deprecated-links-and-tools says "To get the latest LANGID definitions go to
+ * https://docs.microsoft.com/en-us/windows/desktop/intl/language-identifier-constants-and-strings. This
+ * page will change as new LANGIDs are added." That page has no list of LANGIDs, but says "For the
+ * predefined primary language identifiers with their valid sublanguage identifiers, see
+ * [\[MS-LCID\]: Windows Language Code Identifier (LCID)
+ * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-565326e84c37f)."
+ * That page at the time of this writing as a PDF marked as version 16.0, dated 2024-04-24:
+ * https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf
+ * [MS-LCID] defines an LCID as a 32-bit value consisting of a 16-bit a "Language ID", a 4-bit "Sort
+ * ID", and 12 reserved bits.
+ *
+ * That is to say: The USB-IF has said in essence "USB LANGIDs are defined to be the 'Language ID'
+ * portion of Microsoft Windows LCIDs (Language Code Identifiers); refer to Microsoft's published
+ * list of LCID Language IDs for a list of USB LANGIDs."
+ *
+ * The scheme for Language IDs is that the least-significant-byte is "primary" ID and the
+ * most-significant-byte is a "sublanguage" ID within that. With this in mind, Microsoft's choice
+ * to sort their list of by most-significant-byte was a poor editorial choice.
+ */
+#define LANGID_AR 0x0001
+#define LANGID_AR 0x0001
+#define LANGID_AR_SA 0x0401
+#define LANGID_AR_SA 0x0401
+#define LANGID_QPS_PLOC 0x0501
+#define LANGID_QPS_PLOC 0x0501
+#define LANGID_AR_IQ 0x0801
+#define LANGID_AR_IQ 0x0801
+#define LANGID_AR_EG 0x0C01
+#define LANGID_AR_EG 0x0C01
+#define LANGID_AR_LY 0x1001
+#define LANGID_AR_LY 0x1001
+#define LANGID_AR_DZ 0x1401
+#define LANGID_AR_DZ 0x1401
+#define LANGID_AR_MA 0x1801
+#define LANGID_AR_MA 0x1801
+#define LANGID_AR_TN 0x1C01
+#define LANGID_AR_TN 0x1C01
+#define LANGID_AR_OM 0x2001
+#define LANGID_AR_OM 0x2001
+#define LANGID_AR_YE 0x2401
+#define LANGID_AR_YE 0x2401
+#define LANGID_AR_SY 0x2801
+#define LANGID_AR_SY 0x2801
+#define LANGID_AR_JO 0x2C01
+#define LANGID_AR_JO 0x2C01
+#define LANGID_AR_LB 0x3001
+#define LANGID_AR_LB 0x3001
+#define LANGID_AR_KW 0x3401
+#define LANGID_AR_KW 0x3401
+#define LANGID_AR_AE 0x3801
+#define LANGID_AR_AE 0x3801
+#define LANGID_AR_BH 0x3C01
+#define LANGID_AR_BH 0x3C01
+#define LANGID_AR_QA 0x4001
+#define LANGID_AR_QA 0x4001
+#define LANGID_AR_PLOC_SA 0x4401
+#define LANGID_AR_PLOC_SA 0x4401
+#define LANGID_AR_145 0x4801
+#define LANGID_AR_145 0x4801
+#define LANGID_BG 0x0002
+#define LANGID_BG 0x0002
+#define LANGID_BG_BG 0x0402
+#define LANGID_BG_BG 0x0402
+#define LANGID_CA 0x0003
+#define LANGID_CA 0x0003
+#define LANGID_CA_ES 0x0403
+#define LANGID_CA_ES 0x0403
+#define LANGID_CA_ES_VALENCIA 0x0803
+#define LANGID_CA_ES_VALENCIA 0x0803
+#define LANGID_ZH_HANS 0x0004
+#define LANGID_ZH_HANS 0x0004
+#define LANGID_ZH_TW 0x0404
+#define LANGID_ZH_TW 0x0404
+#define LANGID_ZH_CN 0x0804
+#define LANGID_ZH_CN 0x0804
+#define LANGID_ZH_HK 0x0C04
+#define LANGID_ZH_HK 0x0C04
+#define LANGID_ZH_SG 0x1004
+#define LANGID_ZH_SG 0x1004
+#define LANGID_ZH_MO 0x1404
+#define LANGID_ZH_MO 0x1404
+#define LANGID_ZH 0x7804
+#define LANGID_ZH 0x7804
+#define LANGID_ZH_HANT 0x7C04
+#define LANGID_ZH_HANT 0x7C04
+#define LANGID_CS 0x0005
+#define LANGID_CS 0x0005
+#define LANGID_CS_CZ 0x0405
+#define LANGID_CS_CZ 0x0405
+#define LANGID_DA 0x0006
+#define LANGID_DA 0x0006
+#define LANGID_DA_DK 0x0406
+#define LANGID_DA_DK 0x0406
+#define LANGID_DE 0x0007
+#define LANGID_DE 0x0007
+#define LANGID_DE_DE 0x0407
+#define LANGID_DE_DE 0x0407
+#define LANGID_DE_CH 0x0807
+#define LANGID_DE_CH 0x0807
+#define LANGID_DE_AT 0x0C07
+#define LANGID_DE_AT 0x0C07
+#define LANGID_DE_LU 0x1007
+#define LANGID_DE_LU 0x1007
+#define LANGID_DE_LI 0x1407
+#define LANGID_DE_LI 0x1407
+#define LANGID_EL 0x0008
+#define LANGID_EL 0x0008
+#define LANGID_EL_GR 0x0408
+#define LANGID_EL_GR 0x0408
+#define LANGID_EN 0x0009
+#define LANGID_EN 0x0009
+#define LANGID_EN_US 0x0409
+#define LANGID_EN_US 0x0409
+#define LANGID_EN_GB 0x0809
+#define LANGID_EN_GB 0x0809
+#define LANGID_EN_AU 0x0C09
+#define LANGID_EN_AU 0x0C09
+#define LANGID_EN_CA 0x1009
+#define LANGID_EN_CA 0x1009
+#define LANGID_EN_NZ 0x1409
+#define LANGID_EN_NZ 0x1409
+#define LANGID_EN_IE 0x1809
+#define LANGID_EN_IE 0x1809
+#define LANGID_EN_ZA 0x1C09
+#define LANGID_EN_ZA 0x1C09
+#define LANGID_EN_JM 0x2009
+#define LANGID_EN_JM 0x2009
+#define LANGID_EN_029 0x2409
+#define LANGID_EN_029 0x2409
+#define LANGID_EN_BZ 0x2809
+#define LANGID_EN_BZ 0x2809
+#define LANGID_EN_TT 0x2C09
+#define LANGID_EN_TT 0x2C09
+#define LANGID_EN_ZW 0x3009
+#define LANGID_EN_ZW 0x3009
+#define LANGID_EN_PH 0x3409
+#define LANGID_EN_PH 0x3409
+#define LANGID_EN_ID 0x3809
+#define LANGID_EN_ID 0x3809
+#define LANGID_EN_HK 0x3C09
+#define LANGID_EN_HK 0x3C09
+#define LANGID_EN_IN 0x4009
+#define LANGID_EN_IN 0x4009
+#define LANGID_EN_MY 0x4409
+#define LANGID_EN_MY 0x4409
+#define LANGID_EN_SG 0x4809
+#define LANGID_EN_SG 0x4809
+#define LANGID_EN_AE 0x4C09
+#define LANGID_EN_AE 0x4C09
+#define LANGID_EN_BH 0x5009
+#define LANGID_EN_BH 0x5009
+#define LANGID_EN_EG 0x5409
+#define LANGID_EN_EG 0x5409
+#define LANGID_EN_JO 0x5809
+#define LANGID_EN_JO 0x5809
+#define LANGID_EN_KW 0x5C09
+#define LANGID_EN_KW 0x5C09
+#define LANGID_EN_TR 0x6009
+#define LANGID_EN_TR 0x6009
+#define LANGID_EN_YE 0x6409
+#define LANGID_EN_YE 0x6409
+#define LANGID_ES 0x000A
+#define LANGID_ES 0x000A
+#define LANGID_ES_ES_TRADNL 0x040A
+#define LANGID_ES_ES_TRADNL 0x040A
+#define LANGID_ES_MX 0x080A
+#define LANGID_ES_MX 0x080A
+#define LANGID_ES_ES 0x0C0A
+#define LANGID_ES_ES 0x0C0A
+#define LANGID_ES_GT 0x100A
+#define LANGID_ES_GT 0x100A
+#define LANGID_ES_CR 0x140A
+#define LANGID_ES_CR 0x140A
+#define LANGID_ES_PA 0x180A
+#define LANGID_ES_PA 0x180A
+#define LANGID_ES_DO 0x1C0A
+#define LANGID_ES_DO 0x1C0A
+#define LANGID_ES_VE 0x200A
+#define LANGID_ES_VE 0x200A
+#define LANGID_ES_CO 0x240A
+#define LANGID_ES_CO 0x240A
+#define LANGID_ES_PE 0x280A
+#define LANGID_ES_PE 0x280A
+#define LANGID_ES_AR 0x2C0A
+#define LANGID_ES_AR 0x2C0A
+#define LANGID_ES_EC 0x300A
+#define LANGID_ES_EC 0x300A
+#define LANGID_ES_CL 0x340A
+#define LANGID_ES_CL 0x340A
+#define LANGID_ES_UY 0x380A
+#define LANGID_ES_UY 0x380A
+#define LANGID_ES_PY 0x3C0A
+#define LANGID_ES_PY 0x3C0A
+#define LANGID_ES_BO 0x400A
+#define LANGID_ES_BO 0x400A
+#define LANGID_ES_SV 0x440A
+#define LANGID_ES_SV 0x440A
+#define LANGID_ES_HN 0x480A
+#define LANGID_ES_HN 0x480A
+#define LANGID_ES_NI 0x4C0A
+#define LANGID_ES_NI 0x4C0A
+#define LANGID_ES_PR 0x500A
+#define LANGID_ES_PR 0x500A
+#define LANGID_ES_US 0x540A
+#define LANGID_ES_US 0x540A
+#define LANGID_ES_419 0x580A
+#define LANGID_ES_419 0x580A
+#define LANGID_ES_CU 0x5C0A
+#define LANGID_ES_CU 0x5C0A
+#define LANGID_FI 0x000B
+#define LANGID_FI 0x000B
+#define LANGID_FI_FI 0x040B
+#define LANGID_FI_FI 0x040B
+#define LANGID_FR 0x000C
+#define LANGID_FR 0x000C
+#define LANGID_FR_FR 0x040C
+#define LANGID_FR_FR 0x040C
+#define LANGID_FR_BE 0x080C
+#define LANGID_FR_BE 0x080C
+#define LANGID_FR_CA 0x0C0C
+#define LANGID_FR_CA 0x0C0C
+#define LANGID_FR_CH 0x100C
+#define LANGID_FR_CH 0x100C
+#define LANGID_FR_LU 0x140C
+#define LANGID_FR_LU 0x140C
+#define LANGID_FR_MC 0x180C
+#define LANGID_FR_MC 0x180C
+#define LANGID_FR_029 0x1C0C
+#define LANGID_FR_029 0x1C0C
+#define LANGID_FR_RE 0x200C
+#define LANGID_FR_RE 0x200C
+#define LANGID_FR_CD 0x240C
+#define LANGID_FR_CD 0x240C
+#define LANGID_FR_SN 0x280C
+#define LANGID_FR_SN 0x280C
+#define LANGID_FR_CM 0x2C0C
+#define LANGID_FR_CM 0x2C0C
+#define LANGID_FR_CI 0x300C
+#define LANGID_FR_CI 0x300C
+#define LANGID_FR_ML 0x340C
+#define LANGID_FR_ML 0x340C
+#define LANGID_FR_MA 0x380C
+#define LANGID_FR_MA 0x380C
+#define LANGID_FR_HT 0x3C0C
+#define LANGID_FR_HT 0x3C0C
+#define LANGID_FR_015 0xE40C
+#define LANGID_FR_015 0xE40C
+#define LANGID_HE 0x000D
+#define LANGID_HE 0x000D
+#define LANGID_HE_IL 0x040D
+#define LANGID_HE_IL 0x040D
+#define LANGID_HU 0x000E
+#define LANGID_HU 0x000E
+#define LANGID_HU_HU 0x040E
+#define LANGID_HU_HU 0x040E
+#define LANGID_IS 0x000F
+#define LANGID_IS 0x000F
+#define LANGID_IS_IS 0x040F
+#define LANGID_IS_IS 0x040F
+#define LANGID_IT 0x0010
+#define LANGID_IT 0x0010
+#define LANGID_IT_IT 0x0410
+#define LANGID_IT_IT 0x0410
+#define LANGID_IT_CH 0x0810
+#define LANGID_IT_CH 0x0810
+#define LANGID_JA 0x0011
+#define LANGID_JA 0x0011
+#define LANGID_JA_JP 0x0411
+#define LANGID_JA_JP 0x0411
+#define LANGID_JA_PLOC_JP 0x0811
+#define LANGID_JA_PLOC_JP 0x0811
+#define LANGID_KO 0x0012
+#define LANGID_KO 0x0012
+#define LANGID_KO_KR 0x0412
+#define LANGID_KO_KR 0x0412
+#define LANGID_NL 0x0013
+#define LANGID_NL 0x0013
+#define LANGID_NL_NL 0x0413
+#define LANGID_NL_NL 0x0413
+#define LANGID_NL_BE 0x0813
+#define LANGID_NL_BE 0x0813
+#define LANGID_NO 0x0014
+#define LANGID_NO 0x0014
+#define LANGID_NB_NO 0x0414
+#define LANGID_NB_NO 0x0414
+#define LANGID_NN_NO 0x0814
+#define LANGID_NN_NO 0x0814
+#define LANGID_NN 0x7814
+#define LANGID_NN 0x7814
+#define LANGID_NB 0x7C14
+#define LANGID_NB 0x7C14
+#define LANGID_PL 0x0015
+#define LANGID_PL 0x0015
+#define LANGID_PL_PL 0x0415
+#define LANGID_PL_PL 0x0415
+#define LANGID_PT 0x0016
+#define LANGID_PT 0x0016
+#define LANGID_PT_BR 0x0416
+#define LANGID_PT_BR 0x0416
+#define LANGID_PT_PT 0x0816
+#define LANGID_PT_PT 0x0816
+#define LANGID_RM 0x0017
+#define LANGID_RM 0x0017
+#define LANGID_RM_CH 0x0417
+#define LANGID_RM_CH 0x0417
+#define LANGID_RO 0x0018
+#define LANGID_RO 0x0018
+#define LANGID_RO_RO 0x0418
+#define LANGID_RO_RO 0x0418
+#define LANGID_RO_MD 0x0818
+#define LANGID_RO_MD 0x0818
+#define LANGID_RU 0x0019
+#define LANGID_RU 0x0019
+#define LANGID_RU_RU 0x0419
+#define LANGID_RU_RU 0x0419
+#define LANGID_RU_MD 0x0819
+#define LANGID_RU_MD 0x0819
+#define LANGID_HR 0x001A
+#define LANGID_HR 0x001A
+#define LANGID_HR_HR 0x041A
+#define LANGID_HR_HR 0x041A
+#define LANGID_SR_LATN_CS 0x081A
+#define LANGID_SR_LATN_CS 0x081A
+#define LANGID_SR_CYRL_CS 0x0C1A
+#define LANGID_SR_CYRL_CS 0x0C1A
+#define LANGID_HR_BA 0x101A
+#define LANGID_HR_BA 0x101A
+#define LANGID_BS_LATN_BA 0x141A
+#define LANGID_BS_LATN_BA 0x141A
+#define LANGID_SR_LATN_BA 0x181A
+#define LANGID_SR_LATN_BA 0x181A
+#define LANGID_SR_CYRL_BA 0x1C1A
+#define LANGID_SR_CYRL_BA 0x1C1A
+#define LANGID_BS_CYRL_BA 0x201A
+#define LANGID_BS_CYRL_BA 0x201A
+#define LANGID_SR_LATN_RS 0x241A
+#define LANGID_SR_LATN_RS 0x241A
+#define LANGID_SR_CYRL_RS 0x281A
+#define LANGID_SR_CYRL_RS 0x281A
+#define LANGID_SR_LATN_ME 0x2C1A
+#define LANGID_SR_LATN_ME 0x2C1A
+#define LANGID_SR_CYRL_ME 0x301A
+#define LANGID_SR_CYRL_ME 0x301A
+#define LANGID_BS_CYRL 0x641A
+#define LANGID_BS_CYRL 0x641A
+#define LANGID_BS_LATN 0x681A
+#define LANGID_BS_LATN 0x681A
+#define LANGID_SR_CYRL 0x6C1A
+#define LANGID_SR_CYRL 0x6C1A
+#define LANGID_SR_LATN 0x701A
+#define LANGID_SR_LATN 0x701A
+#define LANGID_BS 0x781A
+#define LANGID_BS 0x781A
+#define LANGID_SR 0x7C1A
+#define LANGID_SR 0x7C1A
+#define LANGID_SK 0x001B
+#define LANGID_SK 0x001B
+#define LANGID_SK_SK 0x041B
+#define LANGID_SK_SK 0x041B
+#define LANGID_SQ 0x001C
+#define LANGID_SQ 0x001C
+#define LANGID_SQ_AL 0x041C
+#define LANGID_SQ_AL 0x041C
+#define LANGID_SV 0x001D
+#define LANGID_SV 0x001D
+#define LANGID_SV_SE 0x041D
+#define LANGID_SV_SE 0x041D
+#define LANGID_SV_FI 0x081D
+#define LANGID_SV_FI 0x081D
+#define LANGID_TH 0x001E
+#define LANGID_TH 0x001E
+#define LANGID_TH_TH 0x041E
+#define LANGID_TH_TH 0x041E
+#define LANGID_TR 0x001F
+#define LANGID_TR 0x001F
+#define LANGID_TR_TR 0x041F
+#define LANGID_TR_TR 0x041F
+#define LANGID_UR 0x0020
+#define LANGID_UR 0x0020
+#define LANGID_UR_PK 0x0420
+#define LANGID_UR_PK 0x0420
+#define LANGID_UR_IN 0x0820
+#define LANGID_UR_IN 0x0820
+#define LANGID_ID 0x0021
+#define LANGID_ID 0x0021
+#define LANGID_ID_ID 0x0421
+#define LANGID_ID_ID 0x0421
+#define LANGID_UK 0x0022
+#define LANGID_UK 0x0022
+#define LANGID_UK_UA 0x0422
+#define LANGID_UK_UA 0x0422
+#define LANGID_BE 0x0023
+#define LANGID_BE 0x0023
+#define LANGID_BE_BY 0x0423
+#define LANGID_BE_BY 0x0423
+#define LANGID_SL 0x0024
+#define LANGID_SL 0x0024
+#define LANGID_SL_SI 0x0424
+#define LANGID_SL_SI 0x0424
+#define LANGID_ET 0x0025
+#define LANGID_ET 0x0025
+#define LANGID_ET_EE 0x0425
+#define LANGID_ET_EE 0x0425
+#define LANGID_LV 0x0026
+#define LANGID_LV 0x0026
+#define LANGID_LV_LV 0x0426
+#define LANGID_LV_LV 0x0426
+#define LANGID_LT 0x0027
+#define LANGID_LT 0x0027
+#define LANGID_LT_LT 0x0427
+#define LANGID_LT_LT 0x0427
+#define LANGID_TG 0x0028
+#define LANGID_TG 0x0028
+#define LANGID_TG_CYRL_TJ 0x0428
+#define LANGID_TG_CYRL_TJ 0x0428
+#define LANGID_TG_CYRL 0x7C28
+#define LANGID_TG_CYRL 0x7C28
+#define LANGID_FA 0x0029
+#define LANGID_FA 0x0029
+#define LANGID_FA_IR 0x0429
+#define LANGID_FA_IR 0x0429
+#define LANGID_VI 0x002A
+#define LANGID_VI 0x002A
+#define LANGID_VI_VN 0x042A
+#define LANGID_VI_VN 0x042A
+#define LANGID_HY 0x002B
+#define LANGID_HY 0x002B
+#define LANGID_HY_AM 0x042B
+#define LANGID_HY_AM 0x042B
+#define LANGID_AZ 0x002C
+#define LANGID_AZ 0x002C
+#define LANGID_AZ_LATN_AZ 0x042C
+#define LANGID_AZ_LATN_AZ 0x042C
+#define LANGID_AZ_CYRL_AZ 0x082C
+#define LANGID_AZ_CYRL_AZ 0x082C
+#define LANGID_AZ_CYRL 0x742C
+#define LANGID_AZ_CYRL 0x742C
+#define LANGID_AZ_LATN 0x782C
+#define LANGID_AZ_LATN 0x782C
+#define LANGID_EU 0x002D
+#define LANGID_EU 0x002D
+#define LANGID_EU_ES 0x042D
+#define LANGID_EU_ES 0x042D
+#define LANGID_HSB 0x002E
+#define LANGID_HSB 0x002E
+#define LANGID_HSB_DE 0x042E
+#define LANGID_HSB_DE 0x042E
+#define LANGID_DSB_DE 0x082E
+#define LANGID_DSB_DE 0x082E
+#define LANGID_DSB 0x7C2E
+#define LANGID_DSB 0x7C2E
+#define LANGID_MK 0x002F
+#define LANGID_MK 0x002F
+#define LANGID_MK_MK 0x042F
+#define LANGID_MK_MK 0x042F
+#define LANGID_ST 0x0030
+#define LANGID_ST 0x0030
+#define LANGID_ST_ZA 0x0430
+#define LANGID_ST_ZA 0x0430
+#define LANGID_TS 0x0031
+#define LANGID_TS 0x0031
+#define LANGID_TS_ZA 0x0431
+#define LANGID_TS_ZA 0x0431
+#define LANGID_TN 0x0032
+#define LANGID_TN 0x0032
+#define LANGID_TN_ZA 0x0432
+#define LANGID_TN_ZA 0x0432
+#define LANGID_TN_BW 0x0832
+#define LANGID_TN_BW 0x0832
+#define LANGID_VE 0x0033
+#define LANGID_VE 0x0033
+#define LANGID_VE_ZA 0x0433
+#define LANGID_VE_ZA 0x0433
+#define LANGID_XH 0x0034
+#define LANGID_XH 0x0034
+#define LANGID_XH_ZA 0x0434
+#define LANGID_XH_ZA 0x0434
+#define LANGID_ZU 0x0035
+#define LANGID_ZU 0x0035
+#define LANGID_ZU_ZA 0x0435
+#define LANGID_ZU_ZA 0x0435
+#define LANGID_AF 0x0036
+#define LANGID_AF 0x0036
+#define LANGID_AF_ZA 0x0436
+#define LANGID_AF_ZA 0x0436
+#define LANGID_KA 0x0037
+#define LANGID_KA 0x0037
+#define LANGID_KA_GE 0x0437
+#define LANGID_KA_GE 0x0437
+#define LANGID_FO 0x0038
+#define LANGID_FO 0x0038
+#define LANGID_FO_FO 0x0438
+#define LANGID_FO_FO 0x0438
+#define LANGID_HI 0x0039
+#define LANGID_HI 0x0039
+#define LANGID_HI_IN 0x0439
+#define LANGID_HI_IN 0x0439
+#define LANGID_MT 0x003A
+#define LANGID_MT 0x003A
+#define LANGID_MT_MT 0x043A
+#define LANGID_MT_MT 0x043A
+#define LANGID_SE 0x003B
+#define LANGID_SE 0x003B
+#define LANGID_SE_NO 0x043B
+#define LANGID_SE_NO 0x043B
+#define LANGID_SE_SE 0x083B
+#define LANGID_SE_SE 0x083B
+#define LANGID_SE_FI 0x0C3B
+#define LANGID_SE_FI 0x0C3B
+#define LANGID_SMJ_NO 0x103B
+#define LANGID_SMJ_NO 0x103B
+#define LANGID_SMJ_SE 0x143B
+#define LANGID_SMJ_SE 0x143B
+#define LANGID_SMA_NO 0x183B
+#define LANGID_SMA_NO 0x183B
+#define LANGID_SMA_SE 0x1C3B
+#define LANGID_SMA_SE 0x1C3B
+#define LANGID_SMS_FI 0x203B
+#define LANGID_SMS_FI 0x203B
+#define LANGID_SMN_FI 0x243B
+#define LANGID_SMN_FI 0x243B
+#define LANGID_SMN 0x703B
+#define LANGID_SMN 0x703B
+#define LANGID_SMS 0x743B
+#define LANGID_SMS 0x743B
+#define LANGID_SMA 0x783B
+#define LANGID_SMA 0x783B
+#define LANGID_SMJ 0x7C3B
+#define LANGID_SMJ 0x7C3B
+#define LANGID_GA 0x003C
+#define LANGID_GA 0x003C
+#define LANGID_GA_IE 0x083C
+#define LANGID_GA_IE 0x083C
+#define LANGID_YI 0x003D
+#define LANGID_YI 0x003D
+#define LANGID_YI_001 0x043D
+#define LANGID_YI_001 0x043D
+#define LANGID_MS 0x003E
+#define LANGID_MS 0x003E
+#define LANGID_MS_MY 0x043E
+#define LANGID_MS_MY 0x043E
+#define LANGID_MS_BN 0x083E
+#define LANGID_MS_BN 0x083E
+#define LANGID_KK 0x003F
+#define LANGID_KK 0x003F
+#define LANGID_KK_KZ 0x043F
+#define LANGID_KK_KZ 0x043F
+#define LANGID_KK_LATN_KZ 0x083F
+#define LANGID_KK_LATN_KZ 0x083F
+#define LANGID_KK_CYRL 0x783F
+#define LANGID_KK_CYRL 0x783F
+#define LANGID_KK_LATN 0x7C3F
+#define LANGID_KK_LATN 0x7C3F
+#define LANGID_KY 0x0040
+#define LANGID_KY 0x0040
+#define LANGID_KY_KG 0x0440
+#define LANGID_KY_KG 0x0440
+#define LANGID_SW 0x0041
+#define LANGID_SW 0x0041
+#define LANGID_SW_KE 0x0441
+#define LANGID_SW_KE 0x0441
+#define LANGID_TK 0x0042
+#define LANGID_TK 0x0042
+#define LANGID_TK_TM 0x0442
+#define LANGID_TK_TM 0x0442
+#define LANGID_UZ 0x0043
+#define LANGID_UZ 0x0043
+#define LANGID_UZ_LATN_UZ 0x0443
+#define LANGID_UZ_LATN_UZ 0x0443
+#define LANGID_UZ_CYRL_UZ 0x0843
+#define LANGID_UZ_CYRL_UZ 0x0843
+#define LANGID_UZ_CYRL 0x7843
+#define LANGID_UZ_CYRL 0x7843
+#define LANGID_UZ_LATN 0x7C43
+#define LANGID_UZ_LATN 0x7C43
+#define LANGID_TT 0x0044
+#define LANGID_TT 0x0044
+#define LANGID_TT_RU 0x0444
+#define LANGID_TT_RU 0x0444
+#define LANGID_BN 0x0045
+#define LANGID_BN 0x0045
+#define LANGID_BN_IN 0x0445
+#define LANGID_BN_IN 0x0445
+#define LANGID_BN_BD 0x0845
+#define LANGID_BN_BD 0x0845
+#define LANGID_PA 0x0046
+#define LANGID_PA 0x0046
+#define LANGID_PA_IN 0x0446
+#define LANGID_PA_IN 0x0446
+#define LANGID_PA_ARAB_PK 0x0846
+#define LANGID_PA_ARAB_PK 0x0846
+#define LANGID_PA_ARAB 0x7C46
+#define LANGID_PA_ARAB 0x7C46
+#define LANGID_GU 0x0047
+#define LANGID_GU 0x0047
+#define LANGID_GU_IN 0x0447
+#define LANGID_GU_IN 0x0447
+#define LANGID_OR 0x0048
+#define LANGID_OR 0x0048
+#define LANGID_OR_IN 0x0448
+#define LANGID_OR_IN 0x0448
+#define LANGID_TA 0x0049
+#define LANGID_TA 0x0049
+#define LANGID_TA_IN 0x0449
+#define LANGID_TA_IN 0x0449
+#define LANGID_TA_LK 0x0849
+#define LANGID_TA_LK 0x0849
+#define LANGID_TE 0x004A
+#define LANGID_TE 0x004A
+#define LANGID_TE_IN 0x044A
+#define LANGID_TE_IN 0x044A
+#define LANGID_KN 0x004B
+#define LANGID_KN 0x004B
+#define LANGID_KN_IN 0x044B
+#define LANGID_KN_IN 0x044B
+#define LANGID_ML 0x004C
+#define LANGID_ML 0x004C
+#define LANGID_ML_IN 0x044C
+#define LANGID_ML_IN 0x044C
+#define LANGID_AS 0x004D
+#define LANGID_AS 0x004D
+#define LANGID_AS_IN 0x044D
+#define LANGID_AS_IN 0x044D
+#define LANGID_MR 0x004E
+#define LANGID_MR 0x004E
+#define LANGID_MR_IN 0x044E
+#define LANGID_MR_IN 0x044E
+#define LANGID_SA 0x004F
+#define LANGID_SA 0x004F
+#define LANGID_SA_IN 0x044F
+#define LANGID_SA_IN 0x044F
+#define LANGID_MN 0x0050
+#define LANGID_MN 0x0050
+#define LANGID_MN_MN 0x0450
+#define LANGID_MN_MN 0x0450
+#define LANGID_MN_MONG_CN 0x0850
+#define LANGID_MN_MONG_CN 0x0850
+#define LANGID_MN_MONG_MN 0x0C50
+#define LANGID_MN_MONG_MN 0x0C50
+#define LANGID_MN_CYRL 0x7850
+#define LANGID_MN_CYRL 0x7850
+#define LANGID_MN_MONG 0x7C50
+#define LANGID_MN_MONG 0x7C50
+#define LANGID_BO 0x0051
+#define LANGID_BO 0x0051
+#define LANGID_BO_CN 0x0451
+#define LANGID_BO_CN 0x0451
+#define LANGID_BO_BT 0x0851
+#define LANGID_BO_BT 0x0851
+#define LANGID_DZ_BT 0x0C51
+#define LANGID_DZ_BT 0x0C51
+#define LANGID_CY 0x0052
+#define LANGID_CY 0x0052
+#define LANGID_CY_GB 0x0452
+#define LANGID_CY_GB 0x0452
+#define LANGID_KM 0x0053
+#define LANGID_KM 0x0053
+#define LANGID_KM_KH 0x0453
+#define LANGID_KM_KH 0x0453
+#define LANGID_LO 0x0054
+#define LANGID_LO 0x0054
+#define LANGID_LO_LA 0x0454
+#define LANGID_LO_LA 0x0454
+#define LANGID_MY 0x0055
+#define LANGID_MY 0x0055
+#define LANGID_MY_MM 0x0455
+#define LANGID_MY_MM 0x0455
+#define LANGID_GL 0x0056
+#define LANGID_GL 0x0056
+#define LANGID_GL_ES 0x0456
+#define LANGID_GL_ES 0x0456
+#define LANGID_KOK 0x0057
+#define LANGID_KOK 0x0057
+#define LANGID_KOK_IN 0x0457
+#define LANGID_KOK_IN 0x0457
+#define LANGID_MNI 0x0058
+#define LANGID_MNI 0x0058
+#define LANGID_MNI_IN 0x0458
+#define LANGID_MNI_IN 0x0458
+#define LANGID_SD 0x0059
+#define LANGID_SD 0x0059
+#define LANGID_SD_DEVA_IN 0x0459
+#define LANGID_SD_DEVA_IN 0x0459
+#define LANGID_SD_ARAB_PK 0x0859
+#define LANGID_SD_ARAB_PK 0x0859
+#define LANGID_SD_ARAB 0x7C59
+#define LANGID_SD_ARAB 0x7C59
+#define LANGID_SYR 0x005A
+#define LANGID_SYR 0x005A
+#define LANGID_SYR_SY 0x045A
+#define LANGID_SYR_SY 0x045A
+#define LANGID_SI 0x005B
+#define LANGID_SI 0x005B
+#define LANGID_SI_LK 0x045B
+#define LANGID_SI_LK 0x045B
+#define LANGID_CHR 0x005C
+#define LANGID_CHR 0x005C
+#define LANGID_CHR_CHER_US 0x045C
+#define LANGID_CHR_CHER_US 0x045C
+#define LANGID_CHR_CHER 0x7C5C
+#define LANGID_CHR_CHER 0x7C5C
+#define LANGID_IU 0x005D
+#define LANGID_IU 0x005D
+#define LANGID_IU_CANS_CA 0x045D
+#define LANGID_IU_CANS_CA 0x045D
+#define LANGID_IU_LATN_CA 0x085D
+#define LANGID_IU_LATN_CA 0x085D
+#define LANGID_IU_CANS 0x785D
+#define LANGID_IU_CANS 0x785D
+#define LANGID_IU_LATN 0x7C5D
+#define LANGID_IU_LATN 0x7C5D
+#define LANGID_AM 0x005E
+#define LANGID_AM 0x005E
+#define LANGID_AM_ET 0x045E
+#define LANGID_AM_ET 0x045E
+#define LANGID_TZM 0x005F
+#define LANGID_TZM 0x005F
+#define LANGID_TZM_ARAB_MA 0x045F
+#define LANGID_TZM_ARAB_MA 0x045F
+#define LANGID_TZM_LATN_DZ 0x085F
+#define LANGID_TZM_LATN_DZ 0x085F
+#define LANGID_TMZ_MA 0x0C5F
+#define LANGID_TMZ_MA 0x0C5F
+#define LANGID_TZM_TFNG_MA 0x105F
+#define LANGID_TZM_TFNG_MA 0x105F
+#define LANGID_TZM_TFNG 0x785F
+#define LANGID_TZM_TFNG 0x785F
+#define LANGID_TZM_LATN 0x7C5F
+#define LANGID_TZM_LATN 0x7C5F
+#define LANGID_KS 0x0060
+#define LANGID_KS 0x0060
+#define LANGID_KS_ARAB 0x0460
+#define LANGID_KS_ARAB 0x0460
+#define LANGID_KS_DEVA_IN 0x0860
+#define LANGID_KS_DEVA_IN 0x0860
+#define LANGID_NE 0x0061
+#define LANGID_NE 0x0061
+#define LANGID_NE_NP 0x0461
+#define LANGID_NE_NP 0x0461
+#define LANGID_NE_IN 0x0861
+#define LANGID_NE_IN 0x0861
+#define LANGID_FY 0x0062
+#define LANGID_FY 0x0062
+#define LANGID_FY_NL 0x0462
+#define LANGID_FY_NL 0x0462
+#define LANGID_PS 0x0063
+#define LANGID_PS 0x0063
+#define LANGID_PS_AF 0x0463
+#define LANGID_PS_AF 0x0463
+#define LANGID_FIL 0x0064
+#define LANGID_FIL 0x0064
+#define LANGID_FIL_PH 0x0464
+#define LANGID_FIL_PH 0x0464
+#define LANGID_DV 0x0065
+#define LANGID_DV 0x0065
+#define LANGID_DV_MV 0x0465
+#define LANGID_DV_MV 0x0465
+#define LANGID_BIN 0x0066
+#define LANGID_BIN 0x0066
+#define LANGID_BIN_NG 0x0466
+#define LANGID_BIN_NG 0x0466
+#define LANGID_FF 0x0067
+#define LANGID_FF 0x0067
+#define LANGID_FF_NG 0x0467
+#define LANGID_FF_NG 0x0467
+#define LANGID_FF_LATN_SN 0x0867
+#define LANGID_FF_LATN_SN 0x0867
+#define LANGID_FF_LATN 0x7C67
+#define LANGID_FF_LATN 0x7C67
+#define LANGID_HA 0x0068
+#define LANGID_HA 0x0068
+#define LANGID_HA_LATN_NG 0x0468
+#define LANGID_HA_LATN_NG 0x0468
+#define LANGID_HA_LATN 0x7C68
+#define LANGID_HA_LATN 0x7C68
+#define LANGID_IBB 0x0069
+#define LANGID_IBB 0x0069
+#define LANGID_IBB_NG 0x0469
+#define LANGID_IBB_NG 0x0469
+#define LANGID_YO 0x006A
+#define LANGID_YO 0x006A
+#define LANGID_YO_NG 0x046A
+#define LANGID_YO_NG 0x046A
+#define LANGID_QUZ 0x006B
+#define LANGID_QUZ 0x006B
+#define LANGID_QUZ_BO 0x046B
+#define LANGID_QUZ_BO 0x046B
+#define LANGID_QUZ_EC 0x086B
+#define LANGID_QUZ_EC 0x086B
+#define LANGID_NSO 0x006C
+#define LANGID_NSO 0x006C
+#define LANGID_NSO_ZA 0x046C
+#define LANGID_NSO_ZA 0x046C
+#define LANGID_BA 0x006D
+#define LANGID_BA 0x006D
+#define LANGID_BA_RU 0x046D
+#define LANGID_BA_RU 0x046D
+#define LANGID_LB 0x006E
+#define LANGID_LB 0x006E
+#define LANGID_LB_LU 0x046E
+#define LANGID_LB_LU 0x046E
+#define LANGID_KL 0x006F
+#define LANGID_KL 0x006F
+#define LANGID_KL_GL 0x046F
+#define LANGID_KL_GL 0x046F
+#define LANGID_IG 0x0070
+#define LANGID_IG 0x0070
+#define LANGID_IG_NG 0x0470
+#define LANGID_IG_NG 0x0470
+#define LANGID_KR 0x0071
+#define LANGID_KR 0x0071
+#define LANGID_KR_LATN_NG 0x0471
+#define LANGID_KR_LATN_NG 0x0471
+#define LANGID_OM 0x0072
+#define LANGID_OM 0x0072
+#define LANGID_OM_ET 0x0472
+#define LANGID_OM_ET 0x0472
+#define LANGID_TI 0x0073
+#define LANGID_TI 0x0073
+#define LANGID_TI_ET 0x0473
+#define LANGID_TI_ET 0x0473
+#define LANGID_TI_ER 0x0873
+#define LANGID_TI_ER 0x0873
+#define LANGID_GN 0x0074
+#define LANGID_GN 0x0074
+#define LANGID_GN_PY 0x0474
+#define LANGID_GN_PY 0x0474
+#define LANGID_HAW 0x0075
+#define LANGID_HAW 0x0075
+#define LANGID_HAW_US 0x0475
+#define LANGID_HAW_US 0x0475
+#define LANGID_LA 0x0076
+#define LANGID_LA 0x0076
+#define LANGID_LA_VA 0x0476
+#define LANGID_LA_VA 0x0476
+#define LANGID_SO 0x0077
+#define LANGID_SO 0x0077
+#define LANGID_SO_SO 0x0477
+#define LANGID_SO_SO 0x0477
+#define LANGID_II 0x0078
+#define LANGID_II 0x0078
+#define LANGID_II_CN 0x0478
+#define LANGID_II_CN 0x0478
+#define LANGID_PAP 0x0079
+#define LANGID_PAP 0x0079
+#define LANGID_PAP_029 0x0479
+#define LANGID_PAP_029 0x0479
+#define LANGID_ARN 0x007A
+#define LANGID_ARN 0x007A
+#define LANGID_ARN_CL 0x047A
+#define LANGID_ARN_CL 0x047A
+#define LANGID_MOH 0x007C
+#define LANGID_MOH 0x007C
+#define LANGID_MOH_CA 0x047C
+#define LANGID_MOH_CA 0x047C
+#define LANGID_BR 0x007E
+#define LANGID_BR 0x007E
+#define LANGID_BR_FR 0x047E
+#define LANGID_BR_FR 0x047E
+#define LANGID_UG 0x0080
+#define LANGID_UG 0x0080
+#define LANGID_UG_CN 0x0480
+#define LANGID_UG_CN 0x0480
+#define LANGID_MI 0x0081
+#define LANGID_MI 0x0081
+#define LANGID_MI_NZ 0x0481
+#define LANGID_MI_NZ 0x0481
+#define LANGID_OC 0x0082
+#define LANGID_OC 0x0082
+#define LANGID_OC_FR 0x0482
+#define LANGID_OC_FR 0x0482
+#define LANGID_CO 0x0083
+#define LANGID_CO 0x0083
+#define LANGID_CO_FR 0x0483
+#define LANGID_CO_FR 0x0483
+#define LANGID_GSW 0x0084
+#define LANGID_GSW 0x0084
+#define LANGID_GSW_FR 0x0484
+#define LANGID_GSW_FR 0x0484
+#define LANGID_SAH 0x0085
+#define LANGID_SAH 0x0085
+#define LANGID_SAH_RU 0x0485
+#define LANGID_SAH_RU 0x0485
+#define LANGID_QUT 0x0086
+#define LANGID_QUT 0x0086
+#define LANGID_QUT_GT 0x0486
+#define LANGID_QUT_GT 0x0486
+#define LANGID_RW 0x0087
+#define LANGID_RW 0x0087
+#define LANGID_RW_RW 0x0487
+#define LANGID_RW_RW 0x0487
+#define LANGID_WO 0x0088
+#define LANGID_WO 0x0088
+#define LANGID_WO_SN 0x0488
+#define LANGID_WO_SN 0x0488
+#define LANGID_PRS 0x008C
+#define LANGID_PRS 0x008C
+#define LANGID_PRS_AF 0x048C
+#define LANGID_PRS_AF 0x048C
+#define LANGID_PLT_MG 0x048D
+#define LANGID_PLT_MG 0x048D
+#define LANGID_ZH_YUE_HK 0x048E
+#define LANGID_ZH_YUE_HK 0x048E
+#define LANGID_TDD_TALE_CN 0x048F
+#define LANGID_TDD_TALE_CN 0x048F
+#define LANGID_KHB_TALU_CN 0x0490
+#define LANGID_KHB_TALU_CN 0x0490
+#define LANGID_GD 0x0091
+#define LANGID_GD 0x0091
+#define LANGID_GD_GB 0x0491
+#define LANGID_GD_GB 0x0491
+#define LANGID_KU 0x0092
+#define LANGID_KU 0x0092
+#define LANGID_KU_ARAB_IQ 0x0492
+#define LANGID_KU_ARAB_IQ 0x0492
+#define LANGID_KU_ARAB 0x7C92
+#define LANGID_KU_ARAB 0x7C92
+#define LANGID_QUC 0x0093
+#define LANGID_QUC 0x0093
+#define LANGID_QUC_CO 0x0493
+#define LANGID_QUC_CO 0x0493
+#define LANGID_QPS_PLOCA 0x05FE
+#define LANGID_QPS_PLOCA 0x05FE
+#define LANGID_QPS_PLOCM 0x09FF
+#define LANGID_QPS_PLOCM 0x09FF
+
+/* USB 2.0 §9.6.6 "Endpoint", field bEndpointAddress, bit 7 */
+#define TUD_ENDPOINT_OUT 0x00
+#define TUD_ENDPOINT_IN 0x80
+
+#endif /* _USB_HELPERS_H_ */
diff --git a/tusb_helpers.h.gen b/tusb_helpers.h.gen
new file mode 100755
index 0000000..4682166
--- /dev/null
+++ b/tusb_helpers.h.gen
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# -*- Mode: C -*-
+set -e
+exec >"${0%.gen}"
+cat <<'EOT'
+/* tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB
+ *
+ * Copyright (c) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef _USB_HELPERS_H_
+#define _USB_HELPERS_H_
+
+/* USB 2.0 §9.6.7 "String" says "The list of currently defined USB LANGIDs can be found at
+ * http://www.usb.org/developers/docs.html.", but that page 404s.
+ *
+ * Once upon a time the USB-IF (usb.org) published a "Language Identifiers (LANGIDs)" version 1.0,
+ * dated 2000-03-29. There is no longer any mention of this on usb.org, but I found a copy at
+ * http://www.baiheee.com/Documents/090518/090518112619/USB_LANGIDs.pdf
+ *
+ * So how does the USB-IF defined LANGIDs these days?
+ *
+ * https://www.usb.org/deprecated-links-and-tools says "To get the latest LANGID definitions go to
+ * https://docs.microsoft.com/en-us/windows/desktop/intl/language-identifier-constants-and-strings. This
+ * page will change as new LANGIDs are added." That page has no list of LANGIDs, but says "For the
+ * predefined primary language identifiers with their valid sublanguage identifiers, see
+ * [\[MS-LCID\]: Windows Language Code Identifier (LCID)
+ * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-565326e84c37f)."
+ * That page at the time of this writing as a PDF marked as version 16.0, dated 2024-04-24:
+ * https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf
+ * [MS-LCID] defines an LCID as a 32-bit value consisting of a 16-bit a "Language ID", a 4-bit "Sort
+ * ID", and 12 reserved bits.
+ *
+ * That is to say: The USB-IF has said in essence "USB LANGIDs are defined to be the 'Language ID'
+ * portion of Microsoft Windows LCIDs (Language Code Identifiers); refer to Microsoft's published
+ * list of LCID Language IDs for a list of USB LANGIDs."
+ *
+ * The scheme for Language IDs is that the least-significant-byte is "primary" ID and the
+ * most-significant-byte is a "sublanguage" ID within that. With this in mind, Microsoft's choice
+ * to sort their list of by most-significant-byte was a poor editorial choice.
+ */
+EOT
+[ -f MS-LCID.pdf ] || wget -O MS-LCID.pdf 'https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf'
+[ -f MS-LCID.txt ] || pdftotext -layout MS-LCID.pdf
+<MS-LCID.txt \
+ grep -E '^\s*0x[0-9A-F]{4}\s+[a-z]' | sed 's/,.*//' | grep -v reserved | # find the lines we're interested in
+ sed -E 's/^\s*0x(..)(..)\s+(\S.*)/\2 \1 \3/p' | tr '[:lower:]-' '[:upper:]_' | # format them as 'PRIhex SUBhex UPPER_STR'
+ sort |
+ sed -E 's/(..) (..) (.*)/#define LANGID_\3 0x\2\1/' | # format them as '#define LANGID_UPPER_STR 0xSUBPRI'
+ column --table --output-separator=' '
+cat <<'EOT'
+
+/* USB 2.0 §9.6.6 "Endpoint", field bEndpointAddress, bit 7 */
+#define TUD_ENDPOINT_OUT 0x00
+#define TUD_ENDPOINT_IN 0x80
+
+#define UTF16(str) u ## str
+
+static inline size_t utf16_strncpy(uint16_t *dst, uint16_t *src, dsize size_t) {
+ size_t i;
+ for (i = 0; i < dsize && src && src[i]; i++)
+ dst[i] = src[i];
+ return i;
+}
+
+#endif /* _USB_HELPERS_H_ */
+EOT
diff --git a/usbkeyboard.c b/usbkeyboard.c
new file mode 100644
index 0000000..436610c
--- /dev/null
+++ b/usbkeyboard.c
@@ -0,0 +1,15 @@
+void usbkeyboard_send_char(uint32_t ch) {
+}
+
+char
+
+enum {
+ IFACE_KEYBOARD = 0
+};
+
+void usbkeyboard_task(void) {
+ if (tud_hid_n_ready(0)) {
+ uint8_t keycode[6]
+ }
+}
+