Embedded Linux could be the USB device, if the kernel support the USB gadget. But the USB-HID(human-interface device) report descriptor does not exist in kernel code, developer needs to fill it by himself.
This article provide a descriptor to support Keyboard and Mouse at the same time. I think it is a good entry point for everyone interested in how to configurate his embedded-linux as an USB device.
My development board is based on Hi3516a, a chip featuring H264/H265 hardware encoding from China
零. Build the necessary kernel modules
In the menuconfig of kernel, you need to set the selections as Module(M):
Device Drivers -> USB support -> your_system_name_usb2.0 usb device support -> your_chip (Maybe it has been built into kernel image)
Device Drivers -> USB support -> USB Gadget Support ->HID Gadget
Save and build the kernel modules:
make XCOMPILER_OR_CONFIG menuconfig
You will find the (dwc_otg.ko and) g_hid.ko has been generated.
Insert the module(s) to your embedded Linux, you may encounter that :
insmod: error inserting './g_hid.ko': -1 No such device
That is, the hid.c file in your_embedded_kernel_folder/drivers/usb/gadget/hid.c is not complete, the platform_device has not been implemented yet.
一. Implement the keyboard and mouse combo in hid.c file :
Add below lines at about line 265 in file hid.c :
MODULE_LICENSE("GPL"); /*hid descriptor for keyboard and mouse*/ static struct hidg_func_descriptor generic_keyboard_and_mouse_combo_data = { .subclass = 0, /*NO SubClass*/ .protocol = 3, /*Keyboard and mouse*/ /*the number is unimportant*/ .report_length = 9, .report_desc_length = 121, .report_desc = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x06, // USAGE (Keyboard) 0xa1, 0x01, // COLLECTION (Application) 0x85, 0x01, //Report ID (1) 0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad) 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x95, 0x08, // REPORT_COUNT (8) 0x75, 0x01, // REPORT_SIZE (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0xFF, // LOGICAL_MAXIMUM (255) 0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x95, 0x05, // REPORT_COUNT (5) 0x75, 0x01, // REPORT_SIZE (1) 0x05, 0x08, // USAGE_PAGE (LEDs) 0x19, 0x01, // USAGE_MINIMUM (Num Lock) 0x29, 0x05, // USAGE_MAXIMUM (Kana) 0x91, 0x02, // OUTPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x03, // REPORT_SIZE (3) 0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0xc0, // END_COLLECTION 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x02, // USAGE (Mouse) 0xa1, 0x01, // COLLECTION (Application) 0x85, 0x02, //Report ID (2) 0x09, 0x01, // USAGE (Pointer) 0xa1, 0x00, // COLLECTION (Physical) 0x05, 0x09, // USAGE_PAGE (Button) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x03, // USAGE_MAXIMUM (Button 3) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x95, 0x03, // REPORT_COUNT (3) 0x75, 0x01, // REPORT_SIZE (1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x05, // REPORT_SIZE (5) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x09, 0x38, // USAGE (Wheel) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 0x25, 0x7f, // LOGICAL_MAXIMUM (127) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x03, // REPORT_COUNT (3) 0x81, 0x06, // INPUT (Data,Var,Rel) 0xc0, // END_COLLECTION 0xc0 // END_COLLECTION }/*report_desc*/ }; static struct platform_device generic_hid_keyboard_and_mouse_combo = { .name = "hidg", .id = 1, .num_resources = 0, .resource = 0, .dev.platform_data = &generic_keyboard_and_mouse_combo_data, }; static int __init hidg_init(void) { int status; #if(1) status = platform_device_register(&generic_hid_keyboard_and_mouse_combo); if (status < 0) { printk("platform_driver hid :*****wrong\n"); platform_device_unregister(&generic_hid_keyboard_and_mouse_combo); return status; } #endif status = platform_driver_probe(&hidg_plat_driver, hidg_plat_driver_probe); if (status < 0) return status; status = usb_composite_probe(&hidg_driver, hid_bind); if (status < 0) platform_driver_unregister(&hidg_plat_driver); return status; } module_init(hidg_init); static void __exit hidg_cleanup(void) { platform_driver_unregister(&hidg_plat_driver); #if(1) platform_device_unregister(&generic_hid_keyboard_and_mouse_combo); #endif usb_composite_unregister(&hidg_driver); } module_exit(hidg_cleanup);
Rebuild and insert the generated g_hid.ko into your embedded linux, there should be no error (maybe with warnings).
Under the /dev folder, there should be a device represents gadget-hid:
~ # ls /dev/hid* /dev/hidg0
二. About the byte array format :
For input (device to host)
Byte 0: indicate it is keyboard (1) or mouse(2) command
Keyboard (byte zeroth = 1)
Byte 1 : external scancodes( left ctrl, left shift, left alt, left meta, right ctrl, right shift, right alt and right meta) represent the bit 0-7, from scancode 0xe0 to e0xe7. 1 as pressed, 0 as released.
Byte 2 : reserved.
Byte 3 to byte 8 : the remained scancode pressed. If there are all zero, means all keys are released.
Mouse (byte zeroth = 2)
Byte 1 : bit 0 represents left button, bit 1 represents right button, and bit 2 represents middle button. 1 means pressed, 0 means released.
Byte 2 : horizontal moving, from -127 (to left) to 127( to right).
Byte 3: vertical moving, from -127(to up) to 127(to bottom).
Byte 4: scrolling, from -127(scrolling down) to 127(scrolling up).
For output (host to device)
Byte 0: indicate it is for keyboard (1). (mouse does not received any message from host)
Byte 1: Indicate the led states : bit 0 is Num lock, bit 1 is Caps lock, and bit 2 is Scr lock. 1 means the led should be on, and 0 means it should be off.
三. Implement a command line program to simulate the keyboard and mouse, the code be :
main_keyboard_and_mouse.c
#include <string.h> #include <stdio.h> #include <ctype.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <stdint.h> #include <inttypes.h> #include <fcntl.h> #include <termios.h> #include <signal.h> //#include <byteswap.h> // // gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2#file-usb_hid_keys-h-L259 // #define KEYBOARD_REPORT_LEN (8) #define MOUSE_REPORT_LEN (4) #define TOTAL_REPORT_LEN (9) #define KEYBOARD_ID (1) #define MOUSE_ID (2) int SetTerminal(int is_show_input, int is_block) { struct termios tio; int file_status; tcgetattr(STDIN_FILENO, &tio); if(0 == is_show_input) tio.c_lflag &= ~(ICANON | ECHO); else tio.c_lflag |= (ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &tio); if(0 == is_block) { file_status = fcntl(STDIN_FILENO, F_GETFL, 0); file_status |= O_NONBLOCK; fcntl(STDIN_FILENO, F_SETFL, file_status); } else { file_status = fcntl(STDIN_FILENO, F_GETFL, 0); file_status &= ~O_NONBLOCK; fcntl(STDIN_FILENO, F_SETFL, file_status); }/*if is_block */ return 0; }/*SetTerminal*/ int IsKeyboardHit(void) { #if(1) int ch; ch = getchar(); if(ch != EOF) { ungetc(ch, stdin); return 1; }/*if */ return 0; #else struct timeval tv; fd_set rdfs; tv.tv_sec = 0; tv.tv_usec = 1000; FD_ZERO(&rdfs); FD_SET (STDIN_FILENO, &rdfs); select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv); return FD_ISSET(STDIN_FILENO, &rdfs); #endif }/*IsKeyboardHit*/ int ToMouseModeData(int character, unsigned char *p_send_buffer) { int c; c = tolower(character); p_send_buffer[0] = MOUSE_ID; switch(c) { #define MOVE_MULTIPLY_CONSTANT (5) case 'a': p_send_buffer[2] = -MOVE_MULTIPLY_CONSTANT; break; case 'd': p_send_buffer[2] = MOVE_MULTIPLY_CONSTANT; break; case 'w': p_send_buffer[3] = -MOVE_MULTIPLY_CONSTANT; break; case 's': p_send_buffer[3] = MOVE_MULTIPLY_CONSTANT; break; case 'f': p_send_buffer[1] ^= 0x01 << 0; break; case 'h': p_send_buffer[1] ^= 0x01 << 1; break; case 'g': p_send_buffer[1] ^= 0x01 << 2; break; case 't': p_send_buffer[4] = 1; break; case 'b': p_send_buffer[4] = -1; break; default: return -1; }/*switch*/ return 0; }/*ToMouseModeData*/ #ifndef _PC_TEST int g_fd = -1; pthread_t g_read_thread; int g_is_read_thread_running = 0; int g_is_end_program = 0; void *ReadTheadRoutine(void *argv) { g_is_read_thread_running = 1; while(0 == g_is_end_program) { struct timeval tv; fd_set rdfs; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&rdfs); FD_SET (g_fd, &rdfs); select(g_fd + 1, &rdfs, NULL, NULL, &tv); if(FD_ISSET(g_fd, &rdfs)) { #define READ_BUFF_LEN (4) uint8_t read_buff[READ_BUFF_LEN]; ssize_t len; int i; if(-1 == g_fd) break; len = read(g_fd, &read_buff[0], READ_BUFF_LEN); printf("read : \r\n"); for(i = 0; i < len; i++) printf("0x%02x ", read_buff[i]); printf("\r\n"); if(2 <= len) { uint8_t keyboard_led_states; keyboard_led_states = read_buff[1]; printf("NUM led "); if((0x01 << 0)& keyboard_led_states) printf("on\r\n"); else printf("off\r\n"); printf("CAPS lock led "); if((0x01 << 1) & keyboard_led_states) printf("on\r\n"); else printf("off\r\n"); printf("SCROLL lock led "); if((0x01 << 2)& keyboard_led_states) printf("on\r\n"); else printf("off\r\n"); if( ((~0x07) & keyboard_led_states) ) { printf("the other leds on : 0x%02x\r\n", ((~0x07) & keyboard_led_states)); } printf("\r\n"); }/*if received*/ }/*if */ }/* while 0 == g_is_end_program*/ return (void*)0; }/*ReadTheadRoutine*/ #endif int ScanCodeToUSBKeyboardReport(unsigned char *p_scan_code, int scan_code_num, unsigned char *p_report_buff) { int i, j; if(NULL == p_scan_code || NULL == p_report_buff) return -1; if(6 < scan_code_num) return -2; memset(&p_report_buff[0], 0, KEYBOARD_REPORT_LEN); for(i = 0; i < 8; i++){ if(0xe0 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 0; if(0xe1 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 1; if(0xe2 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 2; if(0xe3 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 3; if(0xe4 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 4; if(0xe5 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 5; if(0xe6 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 6; if(0xe7 == p_scan_code[i]) p_report_buff[0] |= 0x01<< 7; }/*for i*/ //printf("p_report_buff[0] = 0x%02x\r\n", p_report_buff[0]); j = 2; for(i = 0; i < scan_code_num; i++){ if(!(0xe0 <= p_scan_code[i] && 0xe7 >= p_scan_code[i])){ p_report_buff[j] = p_scan_code[i]; j++; }/*if */ }/*for i*/ return 0; }/*ScanCodeToUSBKeyboardReport*/ int ToKeyboardModeData(unsigned char *p_buffer, int buffer_len, unsigned char *p_send_buffer) { #define NORMAL_KEY_NUM (6) size_t sent_len; int i, j; char *p_temp; uint8_t scan_code[NORMAL_KEY_NUM]; uint64_t read_hex; if(0 == p_buffer) return -1; if(2*sizeof(uint64_t) + 1 < strlen(p_buffer)) { printf("input string is too long \r\n"); return -2; }/*if */ read_hex = strtoll(p_buffer, &p_temp, 16); if(p_temp == (char*)p_buffer) { printf("unknown scan_code = %s\r\n", p_buffer); return -3; }/*if */ { uint8_t temp[KEYBOARD_REPORT_LEN - NORMAL_KEY_NUM]; memcpy(&scan_code[0], (char*)&read_hex, NORMAL_KEY_NUM); memcpy(&temp[0], ((char*)&read_hex + NORMAL_KEY_NUM), (8 - NORMAL_KEY_NUM)); for(i = 0; i < KEYBOARD_REPORT_LEN - NORMAL_KEY_NUM; i++ ){ if(0x0ff&temp[i]) { printf("WARNING : max %d keys, truncate the lower\r\n", NORMAL_KEY_NUM); break; }/*if */ }/*for i*/ for(i = 0; i < KEYBOARD_REPORT_LEN - NORMAL_KEY_NUM; i++ ){ if(0x0ff&temp[i]) scan_code[i] = temp[i]; }/*for i*/ }/*local variable*/ for(i = 0; i< NORMAL_KEY_NUM; i++) printf("0x%02x ", scan_code[i]); printf("\r\n"); p_send_buffer[0] = KEYBOARD_ID; ScanCodeToUSBKeyboardReport(&scan_code[0], NORMAL_KEY_NUM, &p_send_buffer[1]); for(i = 0; i< TOTAL_REPORT_LEN; i++) printf("0x%02x ", p_send_buffer[i]); printf("\r\n"); return 0; }/*KeyboardModeRoutine*/ void InterruptSignalHandlingRoutine(int sig) { unsigned char buff[TOTAL_REPORT_LEN]; int i; printf("release all buttons\r\n"); #ifndef _PC_TEST if(-1 == g_fd) return ; if(0 != g_is_read_thread_running) { int ret; g_is_end_program = 1; pthread_join(g_read_thread, (void*)&ret); }/*if 0 != g_is_end_program */ #endif memset(&buff[0], 0, TOTAL_REPORT_LEN); buff[0] = KEYBOARD_ID; for(i = 0; i< TOTAL_REPORT_LEN; i++) printf("0x%02x ", buff[i]); printf("\r\n"); #ifndef _PC_TEST if(-1 != g_fd) { ssize_t sent_len; sent_len = write(g_fd, &buff[0], TOTAL_REPORT_LEN); //printf("sent_len = %d\r\n", sent_len); }/*if -1 != g_fd*/ #endif memset(&buff[0], 0, TOTAL_REPORT_LEN); buff[0] = MOUSE_ID; for(i = 0; i< TOTAL_REPORT_LEN; i++) printf("0x%02x ", buff[i]); printf("\r\n"); #ifndef _PC_TEST if(-1 != g_fd) { ssize_t sent_len; sent_len = write(g_fd, &buff[0], TOTAL_REPORT_LEN); //printf("sent_len = %d\r\n", sent_len); close(g_fd); g_fd = -1; }/*if -1 != g_fd*/ #endif SetTerminal(1, 1); exit(0); }/*InterruptSignalHandlingRoutine*/ #define BUFF_SIZE (256) int main(int argc, char *argv[]) { char buffer[BUFF_SIZE]; signal(SIGINT, InterruptSignalHandlingRoutine); int is_keyboard_mode; #ifndef _PC_TEST if(argc < 2) { printf("/dev/hidgXX should be argument\r\n"); return -1; } printf("argv[1] = %s\r\n", &argv[1][0]); g_fd = open(argv[1], O_RDWR, 0666); if(-1 == g_fd) { printf("%s open error\r\n", strerror(errno)); return -2; }/*if */ pthread_create(&g_read_thread, NULL, ReadTheadRoutine, &g_fd); #endif is_keyboard_mode = 1; while(1) { unsigned char send_buffer[TOTAL_REPORT_LEN]; memset(&buffer[0], 0, sizeof(buffer)); memset(&send_buffer[0], 0, TOTAL_REPORT_LEN); if(0 != is_keyboard_mode) { if(fgets(&buffer[0], BUFF_SIZE, stdin)) { { int i; int is_re_get_input; int input_len; input_len = strlen(&buffer[0]); is_re_get_input = 0; for(i = 0; i < input_len; i++){ if( ' '== buffer[i]) continue; if('m' == buffer[i] || 'M' == buffer[i]) { is_keyboard_mode = 0; is_re_get_input = 1; printf("enter mouse mode :\r\n"); printf("press k swithing to keyboard mode\r\n"); SetTerminal(0, 1); break; } }/*for */ if(0 != is_re_get_input) continue; }/* 0 != is_keyboard_mode */ if(0 > ToKeyboardModeData(&buffer[0], BUFF_SIZE, &send_buffer[0])) continue; } } else /*if mouse*/ { if(0 != IsKeyboardHit()) { int c; c = getchar(); #if(1) if('k'== c || 'K' == c) { is_keyboard_mode = 1; SetTerminal(1, 1); printf("enter keyboard mode :\r\n"); printf("press m swithing to mouse mode\r\n"); continue; } if(0 > ToMouseModeData(c, &send_buffer[0])) continue; #else c = tolower(c); send_buffer[0] = MOUSE_ID; switch(c) { #define MOVE_MULTIPLY_CONSTANT (5) case 'a': send_buffer[2] = -MOVE_MULTIPLY_CONSTANT; break; case 'd': send_buffer[2] = MOVE_MULTIPLY_CONSTANT; break; case 'w': send_buffer[3] = -MOVE_MULTIPLY_CONSTANT; break; case 's': send_buffer[3] = MOVE_MULTIPLY_CONSTANT; break; case 'f': send_buffer[1] ^= 0x01 << 0; break; case 'h': send_buffer[1] ^= 0x01 << 1; break; case 'g': send_buffer[1] ^= 0x01 << 2; break; case 't': send_buffer[4] = 1; break; case 'b': send_buffer[4] = -1; break; case 'k': is_keyboard_mode = 1; SetTerminal(1, 1); printf("enter keyboard mode :\r\n"); printf("press m swithing to mouse mode\r\n"); continue; default: continue; }/*switch*/ #endif }/*keyboard is hit*/ }/*if */ #ifndef _PC_TEST int sent_len; sent_len = write(g_fd, &send_buffer[0], TOTAL_REPORT_LEN); if(0 >= sent_len) { printf("sent_len = %d\r\n", sent_len); return 0; } usleep(30*1000); memset(&send_buffer[0], 0, TOTAL_REPORT_LEN); if(0 == is_keyboard_mode) send_buffer[0] = MOUSE_ID; else send_buffer[0] = KEYBOARD_ID; sent_len = write(g_fd, &send_buffer[0], TOTAL_REPORT_LEN); if(0 >= sent_len) { printf("sent_len = %d\r\n", sent_len); return 0; } printf("\r\n"); #endif }/*while */ #ifndef _PC_TEST close(g_fd); g_fd = 0; #endif return 0; }/*main*/
To build the code :
your_embedded_linux-gcc main_keyboard_and_mouse.c -lpthread -o usb_keyboard_and_mouse_control
To run the code, the /dev/hidgX should be followed as the argument:
~ # ./usb_keyboard_and_mouse_control /dev/hidg0
The default is keyboard mode, you could input the scancode number directly on the embedded linux terminal while the program has run:
The scancode map your could refer to this.
Press m and press enter, you could switch to mouse mode.
In the mouse mode, your input would not should in the terminal, but the
programming receives it indeed. key WASD as up, down, lef and right. Key D as left button, G as right button, F as middle. Key R as scrolling up, V as scrolling down.
press k , it would switch to keyboard mode immediately (it is not necessary to follow a enter after "k").
If you are interested in keyboard or mouse hid only instead of the combo one, there are files your could reter to.
Known insufficieny:
If the embedded Linux does not plus into the host but the program run, the embedded Linux might be system crash or the program blocking at the writing /dev/hidgX. By default, there is no triggered event while the USB has connected/disconnected. Because USB devices' power would be supported by the host (keyboard/mouse/camera...), or the device waits to be control by the host (mobile phone as USB storage). Thus, the connected/disconnected event is useless in the most scenarios.
沒有留言:
張貼留言