现在大量的ARM CPU芯片都可以支持USB OTG2.0/3.0接口,该接口往往工作为USB Host,用于连接USB设备,同时用该OTG接口用于烧写镜像和固件。不过该接口也可以在Linux下作为USB设备工作,此时ARM板卡可以通过该OTG,将ARM板本身配置为键盘、鼠标,甚至是U盘,将OTG口通过USB线与PC机相连,直接在ARM板和PC机之间进行数据传送。下面以RK3568为实例,将RK3568板的Linux系统上配置为键盘和鼠标工作。
一、Linux配置和编译
1.1 内核驱动配置
通过make menuconfig将OTG口配置为DRD(Dual Role Mode),同时使能Gadget详细如下描述。
Device Drivers --->
[*] USB support --->
<*> DesignWare USB2 DRD Core Support
DWC2 Mode Selection (Dual Role mode) --->
<*> USB Gadget Support --->
--- USB Gadget Support
(500) Maximum VBUS Power usage (2-500 mA)
(2) Number of storage pipeline buffers
<M> USB Gadget Drivers
<M> USB functions configurable through configfs
[*] Mass storage
<M> Gadget Filesyste
< > Function Filesystem
<M> Mass Storage Gadget -- 这个做U盘时选用
1.2 键盘和鼠标的代码
最核心的就是descriptor的描述,将OTG接口配置为hidg功能模块。
键盘的描述,以下时按照101键盘定义的,如果需要支持特定的键盘,可以修改此描述表。
static struct hidg_func_descriptor kvm_hid_keyboard_data = {
.subclass = 0, /* No subclass */
.protocol = 1, /* Keyboard */
.report_length = 8,
.report_desc_length = 63,
.report_desc = {
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
0x09, 0x06, /* USAGE (Keyboard) */
0xa1, 0x01, /* COLLECTION (Application) */
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */
0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
0x75, 0x01, /* REPORT_SIZE (1) */
0x95, 0x08, /* REPORT_COUNT (8) */
0x81, 0x02, /* INPUT (Data,Var,Abs) */
0x95, 0x01, /* REPORT_COUNT (1) */
0x75, 0x08, /* REPORT_SIZE (8) */
0x81, 0x03, /* INPUT (Cnst,Var,Abs) */
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) */
0x95, 0x06, /* REPORT_COUNT (6) */
0x75, 0x08, /* REPORT_SIZE (8) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x65, /* LOGICAL_MAXIMUM (101) */
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
0x19, 0x00, /* USAGE_MINIMUM (Reserved) */
0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
0xc0 /* END_COLLECTION */
}
};
以下是标准三键鼠标的定义。
/*hid descriptor for a mouse*/
static struct hidg_func_descriptor kvm_hid_mouse_data = {
.subclass = 0, /*NO SubClass*/
.protocol = 2, /*Mouse*/
.report_length = 4, //绝对值是6, 相对值是4不然打死调不通,实际很简单。但是没有全面了解协议是很难发现这些细节的
.report_desc_length = 52, //126,//62,//104,
.report_desc={
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
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,Ary,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
}
};
然后给出键盘和鼠标的驱动定义
static struct platform_device kvm_hid_keyboard = {
.name = "hidg",
.id = 0,
.num_resources = 0,
.resource = 0,
.dev.platform_data = &kvm_hid_keyboard_data,
};
static struct platform_device kvm_hid_mouse = {
.name = "hidg",
.id = 1,
.num_resources = 0,
.resource = 0,
.dev.platform_data = &kvm_hid_mouse_data,
};
有了以上的定义和驱动,后续用hidg驱动对键盘和鼠标驱动进行控制和probe即可。
static struct usb_device_descriptor device_desc = {
.bLength = sizeof device_desc,
.bDescriptorType = USB_DT_DEVICE,
/* .bcdUSB = DYNAMIC */
/* .bDeviceClass = USB_CLASS_COMM, */
/* .bDeviceSubClass = 0, */
/* .bDeviceProtocol = 0, */
.bDeviceClass = USB_CLASS_PER_INTERFACE,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
/* .bMaxPacketSize0 = f(hardware) */
/* Vendor and product id can be overridden by module parameters. */
.idVendor = cpu_to_le16(HIDG_VENDOR_NUM),
.idProduct = cpu_to_le16(HIDG_PRODUCT_NUM),
/* .bcdDevice = f(hardware) */
/* .iManufacturer = DYNAMIC */
/* .iProduct = DYNAMIC */
/* NO SERIAL NUMBER */
.bNumConfigurations = 1,
};
static struct usb_composite_driver hidg_driver = {
.name = "g_hid",
.dev = &device_desc,
.strings = dev_strings,
.max_speed = USB_SPEED_HIGH,
.bind = hid_bind,
.unbind = hid_unbind,
};
static struct platform_driver hidg_plat_driver = {
.remove = hidg_plat_driver_remove,
.driver = {
.name = "hidg",
},
};
注意这几个驱动加载的顺序
static int __init hidg_init(void)
{
int status;
status = platform_device_register(&kvm_hid_mouse);
if (status < 0) {
return status;
}
status = platform_device_register(&kvm_hid_keyboard);
if (status < 0) {
return status;
}
status = platform_driver_probe(&hidg_plat_driver,
hidg_plat_driver_probe);
if (status < 0)
return status;
status = usb_composite_probe(&hidg_driver);
if (status < 0)
platform_driver_unregister(&hidg_plat_driver);
return status;
}
module_init(hidg_init);
static void __exit hidg_cleanup(void)
{
usb_composite_unregister(&hidg_driver);
platform_driver_unregister(&hidg_plat_driver);
platform_device_unregister(&kvm_hid_keyboard);
platform_device_unregister(&kvm_hid_mouse);
}
module_exit(hidg_cleanup);
二、ARM板Linux系统上USB键盘和鼠标模拟
加载以上模块后,在OTG的USB口插入键盘和鼠标,系统就可以找到键盘和鼠标。然后用编程的方式可以控制相连的对端主机设备。
根据配置的不同,有可能会需要从命令行中切换OTG口为USB device或者host。