参考文章:Linux USB 3.0驱动分析(五)——USB Hub代码分析 - luoyuna - 博客园
USB设备控制器端点缓冲区的优化技术 - 接口/总线/驱动 - 电子发烧友网
摘抄:从物理层的角度来看,端点是一块存储器区域(FIFO),用以缓冲实际接收到或待发送的数据包。
前篇文章:usb hub驱动_ask_true的博客-CSDN博客
一、hub的初始化 ,对《usb hub驱动》篇的补充。
hub_configure函数:
int hub_configure(struct usb_hub *hub,struct usb_endpoint_descriptor *endpoint)
{
struct usb_hcd *hcd;
struct usb_device *hdev = hub->hdev;
struct device *hub_dev = hub->intfdev;
unsigned int pipe;
hub->descriptor = kzalloc(sizeof(*hub->descriptor), GFP_KERNEL);
get_hub_descriptor(hdev, hub->descriptor);
maxchild = hub->descriptor->bNbrPorts;
hub->ports = kzalloc(maxchild * sizeof(struct usb_port *), GFP_KERNEL);
wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
for (i = 0; i < maxchild; i++) {
usb_hub_create_port_device(hub, i + 1);
}
switch (hdev->descriptor.bDeviceProtocol) {
......
}
usb_get_status(hdev, USB_RECIP_DEVICE, 0, &hubstatus);
hub_hub_status(hub, &hubstatus, &hubchange);
//initialize a interrupt pipe
pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress);
hub->urb = usb_alloc_urb(0, GFP_KERNEL);
//initialize a interrupt urb
//USB的中断传输实际上是主机在一定的时间不断地主动轮询设备检查其是否有数据需要传输,
//对此有3个重要参数需要在端点描述符中进行配置:
//1、传输类型 2、轮询时间间隔 3、每次传输的最大数据包大小
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
hub, endpoint->bInterval);
for (i = 0; i < maxchild; i++) {
//该函数前篇已分析,创建hub的每个usb端口设备
ret = usb_hub_create_port_device(hub, i + 1);
}
hub_activate(hub, HUB_INIT);
}
get_hub_descriptor函数:
static int get_hub_descriptor(struct usb_device *hdev,struct usb_hub_descriptor *desc)
{
int i, ret, size;
unsigned dtype;
dtype = USB_DT_HUB;
size = sizeof(struct usb_hub_descriptor);
for (i = 0; i < 3; i++) {
ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
dtype << 8, 0, desc, size,USB_CTRL_GET_TIMEOUT);
if (hub_is_superspeed(hdev)) {
} else if (ret >= USB_DT_HUB_NONVAR_SIZE + 2) {
/* Make sure we have the DeviceRemovable field. */
size = USB_DT_HUB_NONVAR_SIZE + desc->bNbrPorts / 8 + 1;
return ret;
}
}
}
a、 usb_control_msg函数以及各行参的定义见上篇《usb hub驱动》,传入的实参为:
1、USB_DIR_IN | USB_RT_HUB值为1010,000,是Get Hub Descriptor的请求类型,该宏定 义如下:
#define USB_DIR_IN 0x80 /* to host */
#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE)
#define USB_TYPE_CLASS (0x01 << 5)
#define USB_RECIP_DEVICE 0x00
Get Hub Descriptor:
2、 dtype << 8,dtype = USB_DT_HUB,USB_DT_HUB的值为0x29,所以dtype << 8的值为 0x2900;又该参数是16bit的参数,高字节表示描述符类型,低字节表示描述符索引,由 上面的Get Hub Descriptor表可知,描述符类型是0x29,描述符索引是0x00。
USB_DT_HUB的宏定义:
#define USB_DT_HUB (USB_TYPE_CLASS | 0x09)
#define USB_TYPE_CLASS (0x01 << 5)
3、size表示usb hub描述符的大小
b、USB_DT_HUB_NONVAR_SIZE的值为7,表示usb hub描述符中已明确的前7个字节大小(见 前面的usb hub描述符表的size字段)。
USB_DT_HUB_NONVAR_SIZE宏定义:
#define USB_DT_HUB_NONVAR_SIZE 7
c、“size = USB_DT_HUB_NONVAR_SIZE + desc->bNbrPorts / 8 + 1;”语句表示除了usb hub描述符表中的已明确的前7个字节的数据,还得要加上DeviceRemovable字段所表示的数据,该DeviceRemovable字段的大小由hub上的端口数决定。端口是bitmap映射的,一个字节可以表示8个端口,所以desc->bNbrPorts / 8 + 1表示需要多少个字节来表示端口,+1表示最大需要的字节数,因为如果desc->bNbrPorts不能整除,需要再加一个字节的数据大小来存剩下的余值。
回到hub_configure函数里,usb_get_status函数:
int usb_get_status(struct usb_device *dev, int type, int target, void *data)
{
int ret;
//sizeof(*status)里的*status就是前面定义的__le16 *status,所以其大小为2个字节
__le16 *status = kmalloc(sizeof(*status), GFP_KERNEL);
// USB_DIR_IN | type的值为10000000b,Get Status的请求类型;
// type值为0, #define USB_DIR_IN 0x80
// 第五个参数表示想要接受的东西,target为0,参考Get Status表可知是Device的状态。
ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | type, 0, target, status,
sizeof(*status), USB_CTRL_GET_TIMEOUT);
*(u16 *) data = le16_to_cpu(*status);
}
该函数是要获得usb设备的状态。
Get Status:
The Self Powered field indicates whether the device is currently self-powered. If D0 is reset to zero, the device is bus-powered. If D0 is set to one, the device is self-powered. The Self Powered field may not be changed by the SetFeature() or ClearFeature() requests.
注: recipient:接受者,接受的东西
hub_hub_status函数:
static int hub_hub_status(struct usb_hub *hub,u16 *status, u16 *change)
{
int ret;
ret = get_hub_status(hub->hdev, &hub->status->hub);
*status = le16_to_cpu(hub->status->hub.wHubStatus);
*change = le16_to_cpu(hub->status->hub.wHubChange);
}
get_hub_status函数:
static int get_hub_status(struct usb_device *hdev,struct usb_hub_status *data)
{
int i, status = -ETIMEDOUT;
for (i = 0; i < USB_STS_RETRIES &&(status == -ETIMEDOUT || status == -EPIPE); i++) {
//USB_DIR_IN | USB_RT_HUB的值为10100000,是get hub status的请求类型
status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0,
data, sizeof(*data), USB_STS_TIMEOUT);
}
return status;
}
get hub status:
hub_activate函数:
void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
struct usb_device *hdev = hub->hdev;
struct usb_hcd *hcd;
int ret;
int port1;
int status;
bool need_debounce_delay = false;
unsigned delay;
/* Continue a partial initialization */
if (type == HUB_INIT2 || type == HUB_INIT3) {
device_lock(&hdev->dev);
/* Was the hub disconnected while we were waiting? */
if (hub->disconnected)
goto disconnected;
if (type == HUB_INIT2)
goto init2;
goto init3;
}
kref_get(&hub->kref);
/* The superspeed hub except for root hub has to use Hub Depth
* value as an offset into the route string to locate the bits
* it uses to determine the downstream port number. So hub driver
* should send a set hub depth request to superspeed hub after
* the superspeed hub is set configuration in initialization or
* reset procedure.
*
* After a resume, port power should still be on.
* For any other type of activation, turn it on.
*/
if (type != HUB_RESUME) {
if (hdev->parent && hub_is_superspeed(hdev)) {
ret = usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
HUB_SET_DEPTH, USB_RT_HUB,
hdev->level - 1, 0, NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (ret < 0)
dev_err(hub->intfdev,
"[ERROR][USB] set hub depth failed\n");
}
/* Speed up system boot by using a delayed_work for the
* hub's initial power-up delays. This is pretty awkward
* and the implementation looks like a home-brewed sort of
* setjmp/longjmp, but it saves at least 100 ms for each
* root hub (assuming usbcore is compiled into the kernel
* rather than as a module). It adds up.
*
* This can't be done for HUB_RESUME or HUB_RESET_RESUME
* because for those activation types the ports have to be
* operational when we return. In theory this could be done
* for HUB_POST_RESET, but it's easier not to.
*/
if (type == HUB_INIT) {
delay = hub_power_on_good_delay(hub);
hub_power_on(hub, false);
INIT_DELAYED_WORK(&hub->init_work, hub_init_func2);
queue_delayed_work(system_power_efficient_wq,
&hub->init_work,
msecs_to_jiffies(delay));
/* Suppress autosuspend until init is done */
usb_autopm_get_interface_no_resume(
to_usb_interface(hub->intfdev));
return; /* Continues at init2: below */
} else if (type == HUB_RESET_RESUME) {
/* The internal host controller state for the hub device
* may be gone after a host power loss on system resume.
* Update the device's info so the HW knows it's a hub.
*/
hcd = bus_to_hcd(hdev->bus);
if (hcd->driver->update_hub_device) {
ret = hcd->driver->update_hub_device(hcd, hdev,
&hub->tt, GFP_NOIO);
if (ret < 0) {
dev_err(hub->intfdev, "[ERROR][USB] Host not "
"accepting hub info "
"update.\n");
dev_err(hub->intfdev, "[ERROR][USB] LS/FS devices "
"and hubs may not work "
"under this hub\n.");
}
}
hub_power_on(hub, true);
} else {
hub_power_on(hub, true);
}
}
init2:
/*
* Check each port and set hub->change_bits to let hub_wq know
* which ports need attention.
*/
for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
struct usb_port *port_dev = hub->ports[port1 - 1];
struct usb_device *udev = port_dev->child;
u16 portstatus, portchange;
portstatus = portchange = 0;
status = hub_port_status(hub, port1, &portstatus, &portchange);
if (status)
goto abort;
if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
dev_dbg(&port_dev->dev, "[DEBUG][USB] status %04x change %04x\n",
portstatus, portchange);
/*
* After anything other than HUB_RESUME (i.e., initialization
* or any sort of reset), every port should be disabled.
* Unconnected ports should likewise be disabled (paranoia),
* and so should ports for which we have no usb_device.
*/
if ((portstatus & USB_PORT_STAT_ENABLE) && (
type != HUB_RESUME ||
!(portstatus & USB_PORT_STAT_CONNECTION) ||
!udev ||
udev->state == USB_STATE_NOTATTACHED)) {
/*
* USB3 protocol ports will automatically transition
* to Enabled state when detect an USB3.0 device attach.
* Do not disable USB3 protocol ports, just pretend
* power was lost
*/
portstatus &= ~USB_PORT_STAT_ENABLE;
if (!hub_is_superspeed(hdev))
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_ENABLE);
}
/*
* Add debounce if USB3 link is in polling/link training state.
* Link will automatically transition to Enabled state after
* link training completes.
*/
if (hub_is_superspeed(hdev) &&
((portstatus & USB_PORT_STAT_LINK_STATE) ==
USB_SS_PORT_LS_POLLING))
need_debounce_delay = true;
/* Clear status-change flags; we'll debounce later */
if (portchange & USB_PORT_STAT_C_CONNECTION) {
need_debounce_delay = true;
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_CONNECTION);
}
if (portchange & USB_PORT_STAT_C_ENABLE) {
need_debounce_delay = true;
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_ENABLE);
}
if (portchange & USB_PORT_STAT_C_RESET) {
need_debounce_delay = true;
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_RESET);
}
if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
hub_is_superspeed(hub->hdev)) {
need_debounce_delay = true;
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_BH_PORT_RESET);
}
/* We can forget about a "removed" device when there's a
* physical disconnect or the connect status changes.
*/
if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
(portchange & USB_PORT_STAT_C_CONNECTION))
clear_bit(port1, hub->removed_bits);
if (!udev || udev->state == USB_STATE_NOTATTACHED) {
/* Tell hub_wq to disconnect the device or
* check for a new connection or over current condition.
* Based on USB2.0 Spec Section 11.12.5,
* C_PORT_OVER_CURRENT could be set while
* PORT_OVER_CURRENT is not. So check for any of them.
*/
if (udev || (portstatus & USB_PORT_STAT_CONNECTION) ||
(portstatus & USB_PORT_STAT_OVERCURRENT) ||
(portchange & USB_PORT_STAT_C_OVERCURRENT))
set_bit(port1, hub->change_bits);
} else if (portstatus & USB_PORT_STAT_ENABLE) {
bool port_resumed = (portstatus &
USB_PORT_STAT_LINK_STATE) ==
USB_SS_PORT_LS_U0;
/* The power session apparently survived the resume.
* If there was an overcurrent or suspend change
* (i.e., remote wakeup request), have hub_wq
* take care of it. Look at the port link state
* for USB 3.0 hubs, since they don't have a suspend
* change bit, and they don't set the port link change
* bit on device-initiated resume.
*/
if (portchange || (hub_is_superspeed(hub->hdev) &&
port_resumed))
set_bit(port1, hub->change_bits);
} else if (udev->persist_enabled) {
#ifdef CONFIG_PM
udev->reset_resume = 1;
#endif
/* Don't set the change_bits when the device
* was powered off.
*/
if (test_bit(port1, hub->power_bits))
set_bit(port1, hub->change_bits);
} else {
/* The power session is gone; tell hub_wq */
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
set_bit(port1, hub->change_bits);
}
}
/* If no port-status-change flags were set, we don't need any
* debouncing. If flags were set we can try to debounce the
* ports all at once right now, instead of letting hub_wq do them
* one at a time later on.
*
* If any port-status changes do occur during this delay, hub_wq
* will see them later and handle them normally.
*/
if (need_debounce_delay) {
delay = HUB_DEBOUNCE_STABLE;
/* Don't do a long sleep inside a workqueue routine */
if (type == HUB_INIT2) {
INIT_DELAYED_WORK(&hub->init_work, hub_init_func3);
queue_delayed_work(system_power_efficient_wq,
&hub->init_work,
msecs_to_jiffies(delay));
device_unlock(&hdev->dev);
return; /* Continues at init3: below */
} else {
msleep(delay);
}
}
init3:
hub->quiescing = 0;
status = usb_submit_urb(hub->urb, GFP_NOIO);
if (status < 0)
dev_err(hub->intfdev, "[ERROR][USB] activate --> %d\n", status);
if (hub->has_indicators && blinkenlights)
queue_delayed_work(system_power_efficient_wq,
&hub->leds, LED_CYCLE_PERIOD);
/* Scan all ports that need attention */
kick_hub_wq(hub);
abort:
if (type == HUB_INIT2 || type == HUB_INIT3) {
/* Allow autosuspend if it was suppressed */
disconnected:
usb_autopm_put_interface_async(to_usb_interface(hub->intfdev));
device_unlock(&hdev->dev);
}
kref_put(&hub->kref, hub_release);
}
二、hub的工作流程(分析对象:root hub),插拔usb设备会触发主控制器中断。
1、在usb_add_hcd函数中注册了ehci和ohci主控制器中断:
int usb_add_hcd(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags)
{
......
usb_hcd_request_irqs(hcd, irqnum, irqflags);
......
}
usb_hcd_request_irqs函数:
int usb_hcd_request_irqs(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags)
{
if (hcd->driver->irq) {
request_irq(irqnum, &usb_hcd_irq, irqflags,hcd->irq_descr, hcd);
hcd->irq = irqnum;
}
}
中断处理函数usb_hcd_irq:
irqreturn_t usb_hcd_irq (int irq, void *__hcd)
{
struct usb_hcd *hcd = __hcd;
irqreturn_t rc;
if (unlikely(HCD_DEAD(hcd) || !HCD_HW_ACCESSIBLE(hcd)))
rc = IRQ_NONE;
//hcd->driver->irq(hcd)函数指针指向具体的某个主控制的中断处理函数
else if (hcd->driver->irq(hcd) == IRQ_NONE)
rc = IRQ_NONE;
else
rc = IRQ_HANDLED;
return rc;
}
ohci与echi共用一个中断号,具体看《usb主机控制器驱动》篇的dwc2_mux_hcd_init函数和dwc2_create_mux_hcd_pdev函数。
以ohci控制器为例:
static const struct hc_driver ohci_hc_driver = {
.description = hcd_name,
.product_desc = "OHCI Host Controller",
.hcd_priv_size = sizeof(struct ohci_hcd),
.irq = ohci_irq,
.flags = HCD_MEMORY | HCD_USB11,
}
ohci_irq函数:
irqreturn_t ohci_irq (struct usb_hcd *hcd)
{
......
else if (ints & OHCI_INTR_RD) {
ohci_dbg(ohci, "resume detect\n");
ohci_writel(ohci, OHCI_INTR_RD, ®s->intrstatus);
set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
if (ohci->autostop) {
spin_lock (&ohci->lock);
ohci_rh_resume (ohci); //恢复ohci主控制器自身
spin_unlock (&ohci->lock);
} else
usb_hcd_resume_root_hub(hcd);
}
.......
}
usb_hcd_resume_root_hub函数调用层次:
usb_hcd_resume_root_hub
queue_work(pm_wq, &hcd->wakeup_work);
INIT_WORK(&hcd->wakeup_work, hcd_resume_work);
hcd_resume_work
usb_remote_wakeup
usb_autoresume_device
pm_runtime_get_sync
__pm_runtime_resume
rpm_resume
callback = RPM_GET_CALLBACK(dev, runtime_resume);
rpm_callback(callback, dev);
.runtime_resume = usb_runtime_resume
usb_resume_both
usb_resume_interface
/*(struct usb_driver*)*/driver->resume(intf);
最后driver->resume函数指针就是hub_driver里的resume指针
static struct usb_driver hub_driver = {
.name = "hub",
.probe = hub_probe,
.disconnect = hub_disconnect,
.suspend = hub_suspend,
.resume = hub_resume,
.reset_resume = hub_reset_resume,
.pre_reset = hub_pre_reset,
.post_reset = hub_post_reset,
.unlocked_ioctl = hub_ioctl,
.id_table = hub_id_table,
.supports_autosuspend = 1,
};
hub_resume函数:
int hub_resume(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata(intf);
hub_activate(hub, HUB_RESUME);
return 0;
}