linux驅動由淺入深系列:usb子系統之四(android平臺滑鼠驅動程式碼分析)
阿新 • • 發佈:2019-01-02
android上的usb口是支援OTG(on the go)的,USB OTG既可以作為Host又可以作為Device,我們本文來看一下android手機作為Host連線滑鼠的情況。
OTG是如何做到既可以做Host又可以作為Device的呢
標準usb接頭中有四根線:vbus,gnd,dp(d+),dm(d-),android手機上的usb為miniUSB介面增加了一根id線,用來區分Host、Device。
usb是如何檢測裝置插入的呢
裝置插拔檢測都是由hub來進行的,即使不外接hub在USB host controler中也集成了一個roothub。hub上的dp、dm線都有一個15k的下拉電阻拉到低電平,裝置端的dp或dm線上有1.5k的上拉電阻,裝置插入時就會改變dp、dm線上的電平。
當把一個USB裝置插入到一個usb hub的某個埠時,集中器就會檢測到裝置的接入,從而在下一次受到主機通過中斷互動查詢時就會向其報告。集中器的埠在沒有裝置接入時都處於關閉狀態,插入裝置之後也不會自動開啟,必須由主機通過控制交互發出命令予以開啟。所以,在得到集中器的報告之後,主機的USB驅動程式就會為新插入的裝置排程若干個控制互動,並向集中器發出開啟這個埠的命令,這樣新插入的裝置就會出現在USB總線上了,併為該裝置分配唯一的地址。
滑鼠插入android手機後代碼執行過程分析
1,usb滑鼠屬於hid裝置,linux啟動過程中會註冊hid匯流排bus_register(&hid_bus_type)
3,當註冊這個usb_device的時候usb匯流排會呼叫generic driver驅動的probe函式
7,當滑鼠移動或點選時使用input子系統上報Event的程式碼在
[ 86.501420] msm_otg 78db000.usb: phy_reset: success
[ 86.626156] msm_otg 78db000.usb: USB exited from low power mode
[ 86.670053] msm_otg 78db000.usb: phy_reset: success
[ 86.776425] msm_hsusb_host msm_hsusb_host: EHCI Host Controller
[ 86.787250] msm_hsusb_host msm_hsusb_host: new USB bus registered, assigned bus number 1
[ 86.803480] msm_hsusb_host msm_hsusb_host: irq 49, io mem 0x078db000
[ 86.824675] msm_hsusb_host msm_hsusb_host: USB 2.0 started, EHCI 1.00
[ 86.831268] radia usb_new_device
[ 86.836682] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002
[ 86.842444] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[ 86.850085] usb usb1: Product: EHCI Host Controller
[ 86.854990] usb usb1: Manufacturer: Linux 3.18.31-g6649b1f-dirty ehci_hcd
[ 86.861286] usb usb1: SerialNumber: msm_hsusb_host
[ 86.876553] radia generic_probe
[ 86.882021] hub 1-0:1.0: USB hub found
[ 86.885627] hub 1-0:1.0: 1 port detected
[ 87.085676] radia hub_port_connect
[ 87.204811] usb 1-1: new low-speed USB device number 2 using msm_hsusb_host
[ 87.358301] radia usb_new_device
[ 87.372341] usb 1-1: New USB device found, idVendor=10c4, idProduct=8108
[ 87.378352] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 87.385665] usb 1-1: Product: USB OPTICAL MOUSE
[ 87.389643] usb 1-1: Manufacturer: YSPRINGTECH
[ 87.402281] radia generic_probe
[ 87.432953] input: YSPRINGTECH USB OPTICAL MOUSE as /devices/soc/78db000.usb/msm_hsusb_host/usb1/1-1/1-1:1.0/0003:10C4:8108.0001/input/input9
[ 87.461748] hid-generic 0003:10C4:8108.0001: input,hidraw0: USB HID v1.11 Mouse [YSPRINGTECH USB OPTICAL MOUSE] on usb-msm_hsusb_host-1/input0
[ 87.815343] SELinux: initialized (dev fuse, type fuse), uses mountpoint labeling
OTG是如何做到既可以做Host又可以作為Device的呢
標準usb接頭中有四根線:vbus,gnd,dp(d+),dm(d-),android手機上的usb為miniUSB介面增加了一根id線,用來區分Host、Device。
usb是如何檢測裝置插入的呢
裝置插拔檢測都是由hub來進行的,即使不外接hub在USB host controler中也集成了一個roothub。hub上的dp、dm線都有一個15k的下拉電阻拉到低電平,裝置端的dp或dm線上有1.5k的上拉電阻,裝置插入時就會改變dp、dm線上的電平。
當把一個USB裝置插入到一個usb hub的某個埠時,集中器就會檢測到裝置的接入,從而在下一次受到主機通過中斷互動查詢時就會向其報告。集中器的埠在沒有裝置接入時都處於關閉狀態,插入裝置之後也不會自動開啟,必須由主機通過控制交互發出命令予以開啟。所以,在得到集中器的報告之後,主機的USB驅動程式就會為新插入的裝置排程若干個控制互動,並向集中器發出開啟這個埠的命令,這樣新插入的裝置就會出現在USB總線上了,併為該裝置分配唯一的地址。
滑鼠插入android手機後代碼執行過程分析
1,usb滑鼠屬於hid裝置,linux啟動過程中會註冊hid匯流排bus_register(&hid_bus_type)
drivers/hid/hid-core.c
static int __init hid_init(void)
{
int ret;
ret = bus_register(&hid_bus_type);
ret = hidraw_init();
return 0;
}
2,usb滑鼠插入後的會呼叫到hub_port_connect()其中會呼叫usb_alloc_dev建立一個usb_device裝置,然後呼叫usb_new_device-->usb_enumerate_device-->usb_get_configuration-->usb_parse_configuration-->usb_parse_interface(會分配struct usb_interface_cache)-->usb_parse_endpoint分析裝置各描述符,最後usb_new_device會呼叫device_add 這裡把這個usb_device註冊到usb匯流排下。drivers\usb\core\hub.c static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange) { int status, i; unsigned unit_load; struct usb_device *hdev = hub->hdev; struct usb_hcd *hcd = bus_to_hcd(hdev->bus); struct usb_port *port_dev = hub->ports[port1 - 1]; struct usb_device *udev = port_dev->child; static int unreliable_port = -1; printk("radia hub_port_connect\n");//////////////////新增log status = 0; for (i = 0; i < SET_CONFIG_TRIES; i++) { /* reallocate for each attempt, since references * to the previous one can escape in various ways */ udev = usb_alloc_dev(hdev, hdev->bus, port1);////////////////建立一個usb_device裝置 if (!udev) { dev_err(&port_dev->dev, "couldn't allocate usb_device\n"); goto done; } ........ /* Run it through the hoops (find a driver, etc) */ if (!status) { status = usb_new_device(udev);////////////////////////呼叫usb_new_device-->usb_enumerate_device列舉裝置 if (status) { mutex_lock(&usb_port_peer_mutex); spin_lock_irq(&device_state_lock); port_dev->child = NULL; spin_unlock_irq(&device_state_lock); mutex_unlock(&usb_port_peer_mutex); } else { if (hcd->usb_phy && !hdev->parent) usb_phy_notify_connect(hcd->usb_phy, udev->speed); } } }
3,當註冊這個usb_device的時候usb匯流排會呼叫generic driver驅動的probe函式
drivers\usb\core\generic.c
static int generic_probe(struct usb_device *udev)
{
int err, c;
printk("radia generic_probe\n");//////////////////新增log
if (udev->authorized == 0)
dev_err(&udev->dev, "Device is not authorized for usage\n");
else {
c = usb_choose_configuration(udev);//////////////////////////選擇合適的configuration
if (c >= 0) {
err = usb_set_configuration(udev, c);/////////////////////////配置合適的configuration
if (err && err != -ENODEV) {
dev_err(&udev->dev, "can't set config #%d, error %d\n",
c, err);
/* This need not be fatal. The user can try to
* set other configurations. */
}
}
}
/* USB device state == configured ... usable */
usb_notify_add_device(udev);
return 0;
}
這個函式主要為這個usb_device選擇一個合適的配置configuration,並且設定這個configuration4,usb_set_configuration這個函式為這個配置的每個介面初始化為usb_if_device_type型別的裝置。
usb_interface裝置是掛載usb_device下面的,並且這個usb_device可能有多個usb_interface功能裝置。
drivers\usb\core\message.c
int usb_set_configuration(struct usb_device *dev, int configuration)
{
if (dev->authorized == 0 || configuration == -1)
configuration = 0;
else {
for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
if (dev->config[i].desc.bConfigurationValue ==
configuration) {
cp = &dev->config[i];
break;
}
}
}
if (cp) {
nintf = cp->desc.bNumInterfaces;
new_interfaces = kmalloc(nintf * sizeof(*new_interfaces),
GFP_NOIO);
if (!new_interfaces) {
dev_err(&dev->dev, "Out of memory\n");
return -ENOMEM;
}
for (; n < nintf; ++n) {
new_interfaces[n] = kzalloc(
sizeof(struct usb_interface),
GFP_NOIO);
if (!new_interfaces[n]) {
dev_err(&dev->dev, "Out of memory\n");
ret = -ENOMEM;
free_interfaces:
while (--n >= 0)
kfree(new_interfaces[n]);
kfree(new_interfaces);
return ret;
}
}
i = dev->bus_mA - usb_get_max_power(dev, cp);
if (i < 0)
dev_warn(&dev->dev, "new config #%d exceeds power "
"limit by %dmA\n",
configuration, -i);
}
/*
* Initialize the new interface structures and the
* hc/hcd/usbcore interface/endpoint state.
*/
for (i = 0; i < nintf; ++i) {
struct usb_interface_cache *intfc;
struct usb_interface *intf;
struct usb_host_interface *alt;
cp->interface[i] = intf = new_interfaces[i];
intfc = cp->intf_cache[i];
intf->altsetting = intfc->altsetting;
intf->num_altsetting = intfc->num_altsetting;
kref_get(&intfc->ref);
alt = usb_altnum_to_altsetting(intf, 0);
/* No altsetting 0? We'll assume the first altsetting.
* We could use a GetInterface call, but if a device is
* so non-compliant that it doesn't have altsetting 0
* then I wouldn't trust its reply anyway.
*/
if (!alt)
alt = &intf->altsetting[0];
intf->intf_assoc =
find_iad(dev, cp, alt->desc.bInterfaceNumber);
intf->cur_altsetting = alt;
usb_enable_interface(dev, intf, true);
intf->dev.parent = &dev->dev;
intf->dev.driver = NULL;
intf->dev.bus = &usb_bus_type;
intf->dev.type = &usb_if_device_type;
intf->dev.groups = usb_interface_groups;
intf->dev.dma_mask = dev->dev.dma_mask;
INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
intf->minor = -1;
device_initialize(&intf->dev);
pm_runtime_no_callbacks(&intf->dev);
dev_set_name(&intf->dev, "%d-%s:%d.%d",
dev->bus->busnum, dev->devpath,
configuration, alt->desc.bInterfaceNumber);
usb_get_dev(dev);
}
kfree(new_interfaces);
ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
NULL, 0, USB_CTRL_SET_TIMEOUT);
/* Now that all the interfaces are set up, register them
* to trigger binding of drivers to interfaces. probe()
* routines may install different altsettings and may
* claim() any interfaces not yet bound. Many class drivers
* need that: CDC, audio, video, etc.
*/
for (i = 0; i < nintf; ++i) {
struct usb_interface *intf = cp->interface[i];
dev_dbg(&dev->dev,
"adding %s (config #%d, interface %d)\n",
dev_name(&intf->dev), configuration,
intf->cur_altsetting->desc.bInterfaceNumber);
device_enable_async_suspend(&intf->dev);
ret = device_add(&intf->dev);
if (ret != 0) {
dev_err(&dev->dev, "device_add(%s) --> %d\n",
dev_name(&intf->dev), ret);
continue;
}
create_intf_ep_devs(intf);
}
usb_autosuspend_device(dev);
return 0;
}
5,最後在註冊這些interface時,註冊函式會呼叫usb匯流排的match函式匹配usb_interface和usb_driver
但是usb_bus_type匯流排只有match函式,沒有probe函式需要呼叫usb_driver的probe函式usb_probe_interface(drivers/usb/core/driver.c),這個函式會真正的呼叫具體usb_driver的probe函式。root hub或者hub也都是usb_device裝置,usb_device下面包含usb_interface功能(function)
drivers/usb/core/driver.c
static int usb_probe_interface(struct device *dev)
{
struct usb_driver *driver = to_usb_driver(dev->driver);
struct usb_interface *intf = to_usb_interface(dev);
struct usb_device *udev = interface_to_usbdev(intf);
const struct usb_device_id *id;
int error = -ENODEV;
int lpm_disable_error;
printk("radia usb_probe_interface\n");//////////////////新增log
/* Carry out a deferred switch to altsetting 0 */
if (intf->needs_altsetting0) {
error = usb_set_interface(udev, intf->altsetting[0].
desc.bInterfaceNumber, 0);
if (error < 0)
goto err;
intf->needs_altsetting0 = 0;
}
error = driver->probe(intf, id);//////////////////呼叫具體裝置的probe
usb_autosuspend_device(udev);
return error;
}
6,usb滑鼠在android程式碼中沒有使用linux中常用的mousedev.c驅動,而是使用了hid-generic驅動,hid-generic.c的程式碼很簡潔kernel\drivers\hid\hid-generic.c
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <asm/unaligned.h>
#include <asm/byteorder.h>
#include <linux/hid.h>
static const struct hid_device_id hid_table[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_GENERIC, HID_ANY_ID, HID_ANY_ID) },
{ }
};
MODULE_DEVICE_TABLE(hid, hid_table);
static struct hid_driver hid_generic = {
.name = "hid-generic",
.id_table = hid_table,
};
module_hid_driver(hid_generic);
MODULE_AUTHOR("Henrik Rydberg");
MODULE_DESCRIPTION("HID generic driver");
MODULE_LICENSE("GPL");
其中module_hid_driver定義在
kernel\include\linux\hid.h
#define module_hid_driver(__hid_driver) \
module_driver(__hid_driver, hid_register_driver, \
hid_unregister_driver)
這個巨集實際上是呼叫 __hid_register_driver()介面註冊一個hid_driver,並把它掛接在hid_bus_type匯流排驅動連結串列上。7,當滑鼠移動或點選時使用input子系統上報Event的程式碼在
drivers\hid\hid-input.c
static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field,
struct hid_usage *usage)
{
struct input_dev *input = hidinput->input;
struct hid_device *device = input_get_drvdata(input);
int max = 0, code;
unsigned long *bit = NULL;
field->hidinput = hidinput;
switch (usage->hid & HID_USAGE_PAGE) {
case HID_UP_UNDEFINED:
goto ignore;
case HID_UP_KEYBOARD:
set_bit(EV_REP, input->evbit);
if ((usage->hid & HID_USAGE) < 256) {
if (!hid_keyboard[usage->hid & HID_USAGE]) goto ignore;
map_key_clear(hid_keyboard[usage->hid & HID_USAGE]);
} else
map_key(KEY_UNKNOWN);
break;
case HID_UP_BUTTON:
code = ((usage->hid - 1) & HID_USAGE);
switch (field->application) {
case HID_GD_MOUSE:
case HID_GD_POINTER: code += BTN_MOUSE; break;////////////////設定滑鼠左鍵的鍵值
case HID_GD_JOYSTICK:
if (code <= 0xf)
code += BTN_JOYSTICK;
else
code += BTN_TRIGGER_HAPPY - 0x10;
break;
case HID_GD_GAMEPAD:
if (code <= 0xf)
code += BTN_GAMEPAD;
else
code += BTN_TRIGGER_HAPPY - 0x10;
break;
default:
switch (field->physical) {
case HID_GD_MOUSE:
case HID_GD_POINTER: code += BTN_MOUSE; break;
case HID_GD_JOYSTICK: code += BTN_JOYSTICK; break;
case HID_GD_GAMEPAD: code += BTN_GAMEPAD; break;
default: code += BTN_MISC;
}
}
map_key(code);
break;
............
case HID_UP_PID:
switch (usage->hid & HID_USAGE) {
case 0xa4: map_key_clear(BTN_DEAD); break;
default: goto ignore;
}
break;
default:
unknown:
if (field->report_size == 1) {
if (field->report->type == HID_OUTPUT_REPORT) {
map_led(LED_MISC);
break;
}
map_key(BTN_MISC);
break;
}
if (field->flags & HID_MAIN_ITEM_RELATIVE) {
map_rel(REL_MISC);
break;
}
map_abs(ABS_MISC);
break;
}
if (usage->type == EV_ABS) {
int a = field->logical_minimum;
int b = field->logical_maximum;
if ((device->quirks & HID_QUIRK_BADPAD) && (usage->code == ABS_X || usage->code == ABS_Y)) {
a = field->logical_minimum = 0;
b = field->logical_maximum = 255;
}
if (field->application == HID_GD_GAMEPAD || field->application == HID_GD_JOYSTICK)
input_set_abs_params(input, usage->code, a, b, (b - a) >> 8, (b - a) >> 4);/////////////////上報鍵值
else input_set_abs_params(input, usage->code, a, b, 0, 0);
input_abs_set_res(input, usage->code,
hidinput_calc_abs_res(field, usage->code));
/* use a larger default input buffer for MT devices */
if (usage->code == ABS_MT_POSITION_X && input->hint_events_per_packet == 0)
input_set_events_per_packet(input, 60);
}
return;
}
8,usb滑鼠插入android手機後的log如下[ 86.501420] msm_otg 78db000.usb: phy_reset: success
[ 86.626156] msm_otg 78db000.usb: USB exited from low power mode
[ 86.670053] msm_otg 78db000.usb: phy_reset: success
[ 86.776425] msm_hsusb_host msm_hsusb_host: EHCI Host Controller
[ 86.787250] msm_hsusb_host msm_hsusb_host: new USB bus registered, assigned bus number 1
[ 86.803480] msm_hsusb_host msm_hsusb_host: irq 49, io mem 0x078db000
[ 86.824675] msm_hsusb_host msm_hsusb_host: USB 2.0 started, EHCI 1.00
[ 86.831268] radia usb_new_device
[ 86.836682] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002
[ 86.842444] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[ 86.850085] usb usb1: Product: EHCI Host Controller
[ 86.854990] usb usb1: Manufacturer: Linux 3.18.31-g6649b1f-dirty ehci_hcd
[ 86.861286] usb usb1: SerialNumber: msm_hsusb_host
[ 86.876553] radia generic_probe
[ 86.882021] hub 1-0:1.0: USB hub found
[ 86.885627] hub 1-0:1.0: 1 port detected
[ 87.085676] radia hub_port_connect
[ 87.204811] usb 1-1: new low-speed USB device number 2 using msm_hsusb_host
[ 87.358301] radia usb_new_device
[ 87.372341] usb 1-1: New USB device found, idVendor=10c4, idProduct=8108
[ 87.378352] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 87.385665] usb 1-1: Product: USB OPTICAL MOUSE
[ 87.389643] usb 1-1: Manufacturer: YSPRINGTECH
[ 87.402281] radia generic_probe
[ 87.432953] input: YSPRINGTECH USB OPTICAL MOUSE as /devices/soc/78db000.usb/msm_hsusb_host/usb1/1-1/1-1:1.0/0003:10C4:8108.0001/input/input9
[ 87.461748] hid-generic 0003:10C4:8108.0001: input,hidraw0: USB HID v1.11 Mouse [YSPRINGTECH USB OPTICAL MOUSE] on usb-msm_hsusb_host-1/input0
[ 87.815343] SELinux: initialized (dev fuse, type fuse), uses mountpoint labeling