diff --git a/Makefile b/Makefile index 8f147e1..53d39d4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean install uninstall debug man +.PHONY: all clean install install-usb-gadget uninstall uninstall-usb-gadget debug man DESTDIR= PREFIX=/usr @@ -36,10 +36,16 @@ install: install -m644 keyd.service $(DESTDIR)$(PREFIX)/lib/systemd/system install -m755 bin/keyd $(DESTDIR)$(PREFIX)/bin install -m644 keyd.1.gz $(DESTDIR)$(PREFIX)/share/man/man1 +install-usb-gadget: install + install -m644 usb-gadget.service $(DESTDIR)$(PREFIX)/lib/systemd/system/keyd-usb-gadget.service + install -m755 src/vkbd/usb-gadget.sh $(DESTDIR)$(PREFIX)/bin/keyd-usb-gadget.sh uninstall: rm -f $(DESTDIR)$(PREFIX)/lib/systemd/system/keyd.service\ bin/keyd $(DESTDIR)$(PREFIX)/bin/keyd\ $(DESTDIR)$(PREFIX)/share/man/man1/keyd.1.gz +uninstall-vkbd-usb-gadget: uninstall + rm -f $(DESTDIR)$(PREFIX)/lib/systemd/system/keyd-usb-gadget.service\ + $(DESTDIR)$(PREFIX)/bin/keyd-usb-gadget.sh test: all @cd t; \ for f in *.sh; do \ diff --git a/src/vkbd/usb-gadget.c b/src/vkbd/usb-gadget.c new file mode 100644 index 0000000..f821e4e --- /dev/null +++ b/src/vkbd/usb-gadget.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include "../keys.h" +#include "usb-gadget.h" + +static uint8_t mods = 0; + +static uint8_t keys[6] = {0}; + +struct vkbd { + int fd; +}; + +struct hid_report { + uint8_t hid_mods; + uint8_t reserved; + uint8_t hid_code[6]; +}; + +static int create_virtual_keyboard(void) +{ + + int fd = open("/dev/hidg0", O_WRONLY | O_NONBLOCK); + if (fd < 0) { + perror("open"); + exit(-1); + } + + return fd; +} + +struct vkbd *vkbd_init(const char *name) +{ + struct vkbd *vkbd = calloc(1, sizeof vkbd); + vkbd->fd = create_virtual_keyboard(); + + return vkbd; +} + +uint16_t hid_code(uint16_t code) +{ + if(hid_table[code]) + return hid_table[code]; + + return 0; +} + +void send_hid_report (const struct vkbd *vkbd) +{ + + struct hid_report report; + + for (int i = 0; i < 6; i++) + report.hid_code[i] = keys[i]; + + report.hid_mods = mods; + + write(vkbd->fd,&report,sizeof(report)); + +} + +static uint8_t get_modifier(int code) +{ + switch (code) { + case KEY_LEFTSHIFT: + return HID_SHIFT; + break; + case KEY_RIGHTSHIFT: + return HID_RIGHTSHIFT; + break; + case KEY_LEFTCTRL: + return HID_CTRL; + break; + case KEY_RIGHTCTRL: + return HID_RIGHTCTRL; + break; + case KEY_LEFTALT: + return HID_ALT; + break; + case KEY_RIGHTALT: + return HID_ALT_GR; + break; + case KEY_LEFTMETA: + return HID_SUPER; + break; + case KEY_RIGHTMETA: + return HID_RIGHTSUPER; + break; + default: + return 0; + break; + } + +} + +static int update_modifier_state(int code, int state) +{ + uint16_t mod = get_modifier(code); + + if(mod) { + if (state) + mods |= mod; + else + mods &= ~mod; + return 0; + } + + return -1; + +} + +void update_key_state(int code, int state) +{ + int i; + int set = 0; + + for (i = 0; i < 6; i++) { + if(keys[i] == code) { + set = 1; + if(state == 0) + keys[i] = 0; + } + } + if(state && !set) { + for (i = 0; i < 6; i++) { + if(keys[i] == 0) { + keys[i] = code; + break; + } + } + } +} + + +void vkbd_send(const struct vkbd *vkbd, int code, int state) +{ + if(update_modifier_state(code, state) < 0) + update_key_state(hid_code(code), state); + + send_hid_report(vkbd); +} + +void vkbd_move_mouse(const struct vkbd *vkbd, int x, int y) +{ + // Not implemented +} + +void free_vkbd(struct vkbd *vkbd) +{ + close(vkbd->fd); + free(vkbd); +} diff --git a/src/vkbd/usb-gadget.h b/src/vkbd/usb-gadget.h new file mode 100644 index 0000000..1ccb61b --- /dev/null +++ b/src/vkbd/usb-gadget.h @@ -0,0 +1,168 @@ +#ifndef _USBGADGET_H_ +#define _USBGADGET_H_ +#define _USBGADGET_H_ + +#ifdef __FreeBSD__ +#include +#else +#include +#endif + +#define HID_CTRL 0x1 +#define HID_RIGHTCTRL 0x10 +#define HID_SHIFT 0x2 +#define HID_RIGHTSHIFT 0x20 +#define HID_ALT 0x4 +#define HID_ALT_GR 0x40 +#define HID_RIGHTSUPER 0x80 +#define HID_SUPER 0x8 + +static const uint16_t hid_table[] = { + [KEY_ESC] = 0x29, + [KEY_1] = 0x1e, + [KEY_2] = 0x1f, + [KEY_3] = 0x20, + [KEY_4] = 0x21, + [KEY_5] = 0x22, + [KEY_6] = 0x23, + [KEY_7] = 0x24, + [KEY_8] = 0x25, + [KEY_9] = 0x26, + [KEY_0] = 0x27, + [KEY_MINUS] = 0x2d, + [KEY_EQUAL] = 0x2e, + [KEY_BACKSPACE] = 0x2a, + [KEY_TAB] = 0x2b, + [KEY_Q] = 0x14, + [KEY_W] = 0x1a, + [KEY_E] = 0x08, + [KEY_R] = 0x15, + [KEY_T] = 0x17, + [KEY_Y] = 0x1c, + [KEY_U] = 0x18, + [KEY_I] = 0x0c, + [KEY_O] = 0x12, + [KEY_P] = 0x13, + [KEY_LEFTBRACE] = 0x2f, + [KEY_RIGHTBRACE] = 0x30, + [KEY_ENTER] = 0x28, + [KEY_LEFTCTRL] = 0xe0, + [KEY_A] = 0x04, + [KEY_S] = 0x16, + [KEY_D] = 0x07, + [KEY_F] = 0x09, + [KEY_G] = 0x0a, + [KEY_H] = 0x0b, + [KEY_J] = 0x0d, + [KEY_K] = 0x0e, + [KEY_L] = 0x0f, + [KEY_SEMICOLON] = 0x33, + [KEY_APOSTROPHE] = 0x34, + [KEY_GRAVE] = 0x35, + [KEY_LEFTSHIFT] = 0xe1, + [KEY_BACKSLASH] = 0x31, + [KEY_Z] = 0x1d, + [KEY_X] = 0x1b, + [KEY_C] = 0x06, + [KEY_V] = 0x19, + [KEY_B] = 0x05, + [KEY_N] = 0x11, + [KEY_M] = 0x10, + [KEY_COMMA] = 0x36, + [KEY_DOT] = 0x37, + [KEY_SLASH] = 0x38, + [KEY_RIGHTSHIFT] = 0xe5, + [KEY_KPASTERISK] = 0x55, + [KEY_LEFTALT] = 0xe2, + [KEY_SPACE] = 0x2c, + [KEY_CAPSLOCK] = 0x39, + [KEY_F1] = 0x3a, + [KEY_F2] = 0x3b, + [KEY_F3] = 0x3c, + [KEY_F4] = 0x3d, + [KEY_F5] = 0x3e, + [KEY_F6] = 0x3f, + [KEY_F7] = 0x40, + [KEY_F8] = 0x41, + [KEY_F9] = 0x42, + [KEY_F10] = 0x43, + [KEY_NUMLOCK] = 0x53, + [KEY_SCROLLLOCK] = 0x47, + [KEY_KP7] = 0x5f, + [KEY_KP8] = 0x60, + [KEY_KP9] = 0x61, + [KEY_KPMINUS] = 0x56, + [KEY_KP4] = 0x5c, + [KEY_KP5] = 0x5d, + [KEY_KP6] = 0x5e, + [KEY_KPPLUS] = 0x57, + [KEY_KP1] = 0x59, + [KEY_KP2] = 0x5a, + [KEY_KP3] = 0x5b, + [KEY_KP0] = 0x62, + [KEY_KPDOT] = 0x63, + [KEY_ZENKAKUHANKAKU] = 0x94, + [KEY_102ND] = 0x64, + [KEY_F11] = 0x44, + [KEY_F12] = 0x45, + [KEY_RO] = 0x87, + [KEY_KATAKANA] = 0x92, + [KEY_HIRAGANA] = 0x93, + [KEY_HENKAN] = 0x8a, + [KEY_KATAKANAHIRAGANA] = 0x88, + [KEY_MUHENKAN] = 0x8b, + [KEY_KPENTER] = 0x58, + [KEY_RIGHTCTRL] = 0xe4, + [KEY_KPSLASH] = 0x54, + [KEY_SYSRQ] = 0x46, + [KEY_RIGHTALT] = 0xe6, + [KEY_HOME] = 0x4a, + [KEY_UP] = 0x52, + [KEY_PAGEUP] = 0x4b, + [KEY_LEFT] = 0x50, + [KEY_RIGHT] = 0x4f, + [KEY_END] = 0x4d, + [KEY_DOWN] = 0x51, + [KEY_PAGEDOWN] = 0x4e, + [KEY_INSERT] = 0x49, + [KEY_DELETE] = 0x4c, + [KEY_MUTE] = 0x7f, + [KEY_VOLUMEDOWN] = 0x81, + [KEY_VOLUMEUP] = 0x80, + [KEY_POWER] = 0x66, + [KEY_KPEQUAL] = 0x67, + [KEY_KPPLUSMINUS] = 0xd7, + [KEY_PAUSE] = 0x48, + [KEY_KPCOMMA] = 0x85, + [KEY_HANGEUL] = 0x90, + [KEY_HANJA] = 0x91, + [KEY_YEN] = 0x89, + [KEY_LEFTMETA] = 0xe3, + [KEY_RIGHTMETA] = 0xe7, + [KEY_COMPOSE] = 0x65, + [KEY_AGAIN] = 0x79, + [KEY_UNDO] = 0x7a, + [KEY_FRONT] = 0x77, + [KEY_COPY] = 0x7c, + [KEY_OPEN] = 0x74, + [KEY_PASTE] = 0x7d, + [KEY_FIND] = 0x7e, + [KEY_CUT] = 0x7b, + [KEY_HELP] = 0x75, + [KEY_KPLEFTPAREN] = 0xb6, + [KEY_KPRIGHTPAREN] = 0xb7, + [KEY_F13] = 0x68, + [KEY_F14] = 0x69, + [KEY_F15] = 0x6a, + [KEY_F16] = 0x6b, + [KEY_F17] = 0x6c, + [KEY_F18] = 0x6d, + [KEY_F19] = 0x6e, + [KEY_F20] = 0x6f, + [KEY_F21] = 0x70, + [KEY_F22] = 0x71, + [KEY_F23] = 0x72, + [KEY_F24] = 0x73 +}; + +#endif diff --git a/src/vkbd/usb-gadget.sh b/src/vkbd/usb-gadget.sh new file mode 100644 index 0000000..0bb7ac5 --- /dev/null +++ b/src/vkbd/usb-gadget.sh @@ -0,0 +1,29 @@ +#!/bin/bash +modprobe libcomposite +cd /sys/kernel/config/usb_gadget || exit +mkdir g1 +cd g1 +mkdir configs/c.1 +mkdir functions/hid.usb0 +echo 1 > functions/hid.usb0/protocol +echo 1 > functions/hid.usb0/subclass +echo 8 > functions/hid.usb0/report_length +echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.usb0/report_desc +mkdir strings/0x409 +mkdir configs/c.1/strings/0x409 +echo 0x0100 > bcdDevice +echo 0x0200 > bcdUSB +echo 0x00 > bDeviceClass +echo 0x00 > bDeviceProtocol +echo 0x00 > bDeviceSubClass +echo 0x08 > bMaxPacketSize0 +echo 0x0104 > idProduct +echo 0x1d6b > idVendor +echo "0123456789" > strings/0x409/serialnumber +echo "Tux" > strings/0x409/manufacturer +echo "USB Gadget Keyboard" > strings/0x409/product +echo "Conf 1" > configs/c.1/strings/0x409/configuration +echo 0x80 > configs/c.1/bmAttributes +echo 500 > configs/c.1/MaxPower +ln -s functions/hid.usb0 configs/c.1 +ls /sys/class/udc > UDC diff --git a/usb-gadget.md b/usb-gadget.md new file mode 100644 index 0000000..5f4e496 --- /dev/null +++ b/usb-gadget.md @@ -0,0 +1,19 @@ +# USB HID gadget + +Linux devices with host and either USB OTG or device port can be used as USB to USB converter boards, with keyboard connected to USB host port and PC to USB OTG or device port. + +In that kind of setup Linux USB HID gadget driver can be used to emulate HID device and `keyd` can be configured to translate evdev input events to HID reports. + + +# Installation + + sudo apt-get install libudev-dev # Debian specific, install the corresponding package on your distribution + + git clone https://github.com/rvaiya/keyd + cd keyd + make vkbd-usb-gadget && sudo make install-usb-gadget + sudo systemctl enable usb-gadget && sudo systemctl start usb-gadget + sudo systemctl enable keyd && sudo systemctl start keyd + +Device should show up on `lsusb` list as `1d6b:0104 Linux Foundation Multifunction Composite Gadget`. +One can also see it in `/dev/input/by-id/` under `Tux_USB_Gadget_Keyboard` name. diff --git a/usb-gadget.service b/usb-gadget.service new file mode 100644 index 0000000..3002f7e --- /dev/null +++ b/usb-gadget.service @@ -0,0 +1,14 @@ +[Unit] +Description=usb gadget setup +Requires=systemd-modules-load.service,keyd.service +Before=keyd.service +After=systemd-modules-load.service + + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/bash /usr/bin/keyd-usb-gadget.sh + +[Install] +WantedBy=keyd.service