mini2440 usb host device controller驱动分析(三)-----root hub驱动的分析(一)
2013-10-17 10:40
453 查看
在我看来,roothub的功能主要有两个:一是感应设备的插拔,在一个是对新插入的设备进行配置。
root hub 是怎么感应到设备的插拔的?
这里开始分析root hub的驱动。之前已经提到过每个host controller都集成有一个root hub。通过看2440的datasheet中的host controller部分,我们可以看到root hub是有相应的物理电路的,并不是host controller虚拟了一个root hub。2440的root hub共有两个port,但是在mini2440上面只连了一个host的接口。
从旁边的图上可以看出,DN0和DP0是第一个host port的引脚。DN1和DP1是第二个host
port的引脚,但是这两个引脚是和udc 的引脚PDN0\PDP0复用的,在mini2440中这个复用的引脚用作了udc的port。但是这里有个问题,是怎么设定这两个复用的引脚的功能的?
root hub的一个很重要的功能就是检测到设备的插拔,有usb device插入时,对设备进行一定部分的初始化。但是有一个需要注意的是,读写usb device,并不需要通过usb root hub,都是hcd直接读写寄存器进行的,不需要通过root hub的传递。root hub对插入的设备进行读写也是通过submit
urb来实现的。
先看root hub的表示。与root hub相关的结构体有两个usb_hub和hub_driver。
在内核中实现了专门针对usb hub的驱动,叫hub_driver。
之前在hcd的初始化驱动中有register_root_hub,这个函数经过device_add等一系列调用,最终会调用到usb hub的probe函数,即hub_probe。(调用到hub_probe时候,可以看到这个函数参数就已经变成了usb_interface,这个过程还需要以后的分析)。
以上就是hub的初始化函数。到probe函数的时候,root hub 就已经用usb_interface来表示了。这个interface有一个interrupt endpoint:ep0,用来进行interrupt传输。hcd是怎么和root hub进行通信的?直接读寄存器应该就可以了,但是看代码觉得是hcd和root hub也用了和其他外部device通信同样的途径。
另外,usb hub还有一个驱动注册的过程,是按照这条线来的:usb_init ---- usb_hub_init ---- usb_register(&hub_driver) 、 kthread_run(hub_thread)。
也就是说,usb_init的时候, 不仅注册了hub_driver, 还启动了一个线程,叫hub_thread。后面我看到这个线程是与设备插拔相关的。后面在分析这个线程。
看完了roothub的初始化,那么root hub是怎么感应到设备的插拔的?
从上面probe函数中的hub_configure函数看起,这个函数很重要。
上面这个函数中重要的是函数的后面部分(前面就是一些初始化过程)。 我们看到usb_hub结构体中有一个 urb成员,在上面程中 用usb_alloc_urb 以及 usb_fill_int_urb对这个成员进行了初始化,后面在hub_active中调用usb_sumbit_urb对该urb进行了提交。初始化时,我们看到这个urb的complete回调函数时hub_irq。也就是说这个urb提交后,udc
core 再把这个urb处理完之后,中断函数再调用时,会调用到hub_irq函数。这个回调的过程我们后面分析。
先继续看hub_active函数。
上面这个函数怪的一点就是把hub_active这个过程 分成了三个不连续的部分去执行。本来是一个函数,却通过shedule_delayed_work这个函数,把这个hub_active进行了分割,使得这个函数 的执行过程变慢。第一阶段将hub power on, 第二阶段 获取各个port的状态。 第三阶段 在submit urb。我觉得是因为这三个阶段之间需要进行延时,而且也不需要精确的延时,所以用schedule_delayed_work来实现。至此,我们已经看到
hub acitve中进行的工作。其中,submit urb是与我们后面 感应设备插拔最相关的。
至此,前期的准备过程已经结束。现在,假设有一个device插入了root hub。那么这个会引起中断,进入ohci_irq函数的处理。在前面的章节中,我们已经看过了这个中断函数。在ohci_irq中,判断usb中断类型,如果是RHSC(root hub status change),就调用usb_hcd_poll_rh_status,获取port的状态, 再调用usb_hcd_giveback_urb,最终可以调用到hub_irq函数。
来看这个函数:
在hub_irq中,通过kick_khubd就会给之前启动的hub_thread发送消息,相当于是进程间通信。 在hub_thread中会继续对事件进行处理。 另外,在hub_irq中会再次提交hub->urb。相当于hub->urb就是负责感应到设备的插拔的。每次感应到之后,hub->urb会再次被submit。
看hub_thread中对事件的处理。hub_thread中会调用hub_events,hub_events 又对不同的事件有不同的处理,如果是设备的插拔,最终会调用到hub_port_connect_change。
可以看到在这个函数中对设备的插拔进行的了处理。具体调用的函数有usb_new_device 以及 usb_disconnect。
至此,我们就看完了 root hub 感应设备插拔的过程。
root hub 是怎么感应到设备的插拔的?
这里开始分析root hub的驱动。之前已经提到过每个host controller都集成有一个root hub。通过看2440的datasheet中的host controller部分,我们可以看到root hub是有相应的物理电路的,并不是host controller虚拟了一个root hub。2440的root hub共有两个port,但是在mini2440上面只连了一个host的接口。
从旁边的图上可以看出,DN0和DP0是第一个host port的引脚。DN1和DP1是第二个host
port的引脚,但是这两个引脚是和udc 的引脚PDN0\PDP0复用的,在mini2440中这个复用的引脚用作了udc的port。但是这里有个问题,是怎么设定这两个复用的引脚的功能的?
root hub的一个很重要的功能就是检测到设备的插拔,有usb device插入时,对设备进行一定部分的初始化。但是有一个需要注意的是,读写usb device,并不需要通过usb root hub,都是hcd直接读写寄存器进行的,不需要通过root hub的传递。root hub对插入的设备进行读写也是通过submit
urb来实现的。
先看root hub的表示。与root hub相关的结构体有两个usb_hub和hub_driver。
struct usb_hub { struct device *intfdev; /* the "interface" device */ struct usb_device *hdev; struct kref kref; struct urb *urb; /* for interrupt polling pipe */ /* buffer for urb ... with extra space in case of babble */ char (*buffer)[8]; dma_addr_t buffer_dma; /* DMA address for buffer */ union { struct usb_hub_status hub; struct usb_port_status port; } *status; /* buffer for status reports */ struct mutex status_mutex; /* for the status buffer */ int error; /* last reported error */ int nerrors; /* track consecutive errors */ struct list_head event_list; /* hubs w/data or errs ready */ unsigned long event_bits[1]; /* status change bitmask */ unsigned long change_bits[1]; /* ports with logical connect status change */ unsigned long busy_bits[1]; /* ports being reset or resumed */ #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ #error event_bits[] is too short! #endif struct usb_hub_descriptor *descriptor; /* class descriptor */ struct usb_tt tt; /* Transaction Translator */ unsigned mA_per_port; /* current for each child */ unsigned limited_power:1; unsigned quiescing:1; unsigned disconnected:1; unsigned has_indicators:1; u8 indicator[USB_MAXCHILDREN]; struct delayed_work leds; struct delayed_work init_work; void **port_owners; };
在内核中实现了专门针对usb hub的驱动,叫hub_driver。
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, .ioctl = hub_ioctl, .id_table = hub_id_table, .supports_autosuspend = 1, };
之前在hcd的初始化驱动中有register_root_hub,这个函数经过device_add等一系列调用,最终会调用到usb hub的probe函数,即hub_probe。(调用到hub_probe时候,可以看到这个函数参数就已经变成了usb_interface,这个过程还需要以后的分析)。
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_host_interface *desc; struct usb_endpoint_descriptor *endpoint; struct usb_device *hdev; struct usb_hub *hub; desc = intf->cur_altsetting; hdev = interface_to_usbdev(intf); endpoint = &desc->endpoint[0].desc; /* We found a hub */ dev_info (&intf->dev, "USB hub found\n"); hub = kzalloc(sizeof(*hub), GFP_KERNEL); if (!hub) { dev_dbg (&intf->dev, "couldn't kmalloc hub struct\n"); return -ENOMEM; } kref_init(&hub->kref); INIT_LIST_HEAD(&hub->event_list); hub->intfdev = &intf->dev; hub->hdev = hdev; INIT_DELAYED_WORK(&hub->leds, led_work); INIT_DELAYED_WORK(&hub->init_work, NULL); usb_get_intf(intf); usb_set_intfdata (intf, hub); intf->needs_remote_wakeup = 1; if (hdev->speed == USB_SPEED_HIGH) highspeed_hubs++; if (hub_configure(hub, endpoint) >= 0) return 0; }
以上就是hub的初始化函数。到probe函数的时候,root hub 就已经用usb_interface来表示了。这个interface有一个interrupt endpoint:ep0,用来进行interrupt传输。hcd是怎么和root hub进行通信的?直接读寄存器应该就可以了,但是看代码觉得是hcd和root hub也用了和其他外部device通信同样的途径。
另外,usb hub还有一个驱动注册的过程,是按照这条线来的:usb_init ---- usb_hub_init ---- usb_register(&hub_driver) 、 kthread_run(hub_thread)。
也就是说,usb_init的时候, 不仅注册了hub_driver, 还启动了一个线程,叫hub_thread。后面我看到这个线程是与设备插拔相关的。后面在分析这个线程。
看完了roothub的初始化,那么root hub是怎么感应到设备的插拔的?
从上面probe函数中的hub_configure函数看起,这个函数很重要。
static 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; u16 hubstatus, hubchange; u16 wHubCharacteristics; unsigned int pipe; int maxp, ret; char *message = "out of memory"; hub->buffer = usb_buffer_alloc(hdev, sizeof(*hub->buffer), GFP_KERNEL, &hub->buffer_dma); hub->status = kmalloc(sizeof(*hub->status), GFP_KERNEL); mutex_init(&hub->status_mutex); hub->descriptor = kmalloc(sizeof(*hub->descriptor), GFP_KERNEL); ret = get_hub_descriptor(hdev, hub->descriptor, sizeof(*hub->descriptor)); hdev->maxchild = hub->descriptor->bNbrPorts; hub->port_owners = kzalloc(hdev->maxchild * sizeof(void *), GFP_KERNEL); wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); spin_lock_init (&hub->tt.lock); INIT_LIST_HEAD (&hub->tt.clear_list); INIT_WORK(&hub->tt.clear_work, hub_tt_work); switch (hdev->descriptor.bDeviceProtocol) { case 0: break; } /* probe() zeroes hub->indicator[] */ ret = usb_get_status(hdev, USB_RECIP_DEVICE, 0, &hubstatus); hcd = bus_to_hcd(hdev->bus); ret = hub_hub_status(hub, &hubstatus, &hubchange); /* set up the interrupt endpoint * We use the EP's maxpacket size instead of (PORTS+1+7)/8 * bytes as USB2.0[11.12.3] says because some hubs are known * to send more data (and thus cause overflow). For root hubs, * maxpktsize is defined in hcd.c's fake endpoint descriptors * to be big enough for at least USB_MAXCHILDREN ports. */ pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe)); hub->urb = usb_alloc_urb(0, GFP_KERNEL); if (!hub->urb) { ret = -ENOMEM; goto fail; } usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval); hub->urb->transfer_dma = hub->buffer_dma; hub->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* maybe cycle the hub leds */ if (hub->has_indicators && blinkenlights) hub->indicator [0] = INDICATOR_CYCLE; hub_activate(hub, HUB_INIT); return 0; }
上面这个函数中重要的是函数的后面部分(前面就是一些初始化过程)。 我们看到usb_hub结构体中有一个 urb成员,在上面程中 用usb_alloc_urb 以及 usb_fill_int_urb对这个成员进行了初始化,后面在hub_active中调用usb_sumbit_urb对该urb进行了提交。初始化时,我们看到这个urb的complete回调函数时hub_irq。也就是说这个urb提交后,udc
core 再把这个urb处理完之后,中断函数再调用时,会调用到hub_irq函数。这个回调的过程我们后面分析。
先继续看hub_active函数。
static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) { struct usb_device *hdev = hub->hdev; int port1; int status; bool need_debounce_delay = false; unsigned delay; /* Continue a partial initialization */ if (type == HUB_INIT2) goto init2; if (type == HUB_INIT3) goto init3; /* After a resume, port power should still be on. * For any other type of activation, turn it on. */ if (type != HUB_RESUME) { if (type == HUB_INIT) { delay = hub_power_on(hub, false); PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2); schedule_delayed_work(&hub->init_work, msecs_to_jiffies(delay)); /* Suppress autosuspend until init is done */ atomic_set(&to_usb_interface(hub->intfdev)-> pm_usage_cnt, 1); return; /* Continues at init2: below */ } else { hub_power_on(hub, true); } } init2: /* Check each port and set hub->change_bits to let khubd know * which ports need attention. */ for (port1 = 1; port1 <= hdev->maxchild; ++port1) { struct usb_device *udev = hdev->children[port1-1]; u16 portstatus, portchange; portstatus = portchange = 0; status = hub_port_status(hub, port1, &portstatus, &portchange); if ((portstatus & USB_PORT_STAT_ENABLE) && ( type != HUB_RESUME || !(portstatus & USB_PORT_STAT_CONNECTION) || !udev || udev->state == USB_STATE_NOTATTACHED)) { clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE); portstatus &= ~USB_PORT_STAT_ENABLE; } /* Clear status-change flags; we'll debounce later */ if (portchange & USB_PORT_STAT_C_CONNECTION) { need_debounce_delay = true; clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_CONNECTION); } if (portchange & USB_PORT_STAT_C_ENABLE) { need_debounce_delay = true; clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_ENABLE); } if (!udev || udev->state == USB_STATE_NOTATTACHED) { /* Tell khubd to disconnect the device or * check for a new connection */ if (udev || (portstatus & USB_PORT_STAT_CONNECTION)) set_bit(port1, hub->change_bits); } else if (portstatus & USB_PORT_STAT_ENABLE) { /* The power session apparently survived the resume. * If there was an overcurrent or suspend change * (i.e., remote wakeup request), have khubd * take care of it. */ if (portchange) set_bit(port1, hub->change_bits); } else if (udev->persist_enabled) { set_bit(port1, hub->change_bits); } else { /* The power session is gone; tell khubd */ usb_set_device_state(udev, USB_STATE_NOTATTACHED); set_bit(port1, hub->change_bits); } } if (need_debounce_delay) { delay = HUB_DEBOUNCE_STABLE; /* Don't do a long sleep inside a workqueue routine */ if (type == HUB_INIT2) { PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func3); schedule_delayed_work(&hub->init_work, msecs_to_jiffies(delay)); 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, "activate --> %d\n", status); if (hub->has_indicators && blinkenlights) schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD); /* Scan all ports that need attention */ kick_khubd(hub); } /* Implement the continuations for the delays above */ static void hub_init_func2(struct work_struct *ws) { struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work); hub_activate(hub, HUB_INIT2); } static void hub_init_func3(struct work_struct *ws) { struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work); hub_activate(hub, HUB_INIT3); }
上面这个函数怪的一点就是把hub_active这个过程 分成了三个不连续的部分去执行。本来是一个函数,却通过shedule_delayed_work这个函数,把这个hub_active进行了分割,使得这个函数 的执行过程变慢。第一阶段将hub power on, 第二阶段 获取各个port的状态。 第三阶段 在submit urb。我觉得是因为这三个阶段之间需要进行延时,而且也不需要精确的延时,所以用schedule_delayed_work来实现。至此,我们已经看到
hub acitve中进行的工作。其中,submit urb是与我们后面 感应设备插拔最相关的。
至此,前期的准备过程已经结束。现在,假设有一个device插入了root hub。那么这个会引起中断,进入ohci_irq函数的处理。在前面的章节中,我们已经看过了这个中断函数。在ohci_irq中,判断usb中断类型,如果是RHSC(root hub status change),就调用usb_hcd_poll_rh_status,获取port的状态, 再调用usb_hcd_giveback_urb,最终可以调用到hub_irq函数。
来看这个函数:
static void hub_irq(struct urb *urb) { struct usb_hub *hub = urb->context; int status = urb->status; unsigned i; unsigned long bits; switch (status) { case -ENOENT: /* synchronous unlink */ case -ECONNRESET: /* async unlink */ case -ESHUTDOWN: /* hardware going away */ return; /* let khubd handle things */ case 0: /* we got data: port status changed */ bits = 0; for (i = 0; i < urb->actual_length; ++i) bits |= ((unsigned long) ((*hub->buffer)[i])) << (i*8); hub->event_bits[0] = bits; break; } hub->nerrors = 0; /* Something happened, let khubd figure it out */ kick_khubd(hub); resubmit: if (hub->quiescing) return; if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0 && status != -ENODEV && status != -EPERM) dev_err (hub->intfdev, "resubmit --> %d\n", status); }
在hub_irq中,通过kick_khubd就会给之前启动的hub_thread发送消息,相当于是进程间通信。 在hub_thread中会继续对事件进行处理。 另外,在hub_irq中会再次提交hub->urb。相当于hub->urb就是负责感应到设备的插拔的。每次感应到之后,hub->urb会再次被submit。
看hub_thread中对事件的处理。hub_thread中会调用hub_events,hub_events 又对不同的事件有不同的处理,如果是设备的插拔,最终会调用到hub_port_connect_change。
/* Handle physical or logical connection change events. * This routine is called when: * a port connection-change occurs; * a port enable-change occurs (often caused by EMI); * usb_reset_and_verify_device() encounters changed descriptors (as from * a firmware download) * caller already locked the hub */ static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange) { struct usb_device *hdev = hub->hdev; struct device *hub_dev = hub->intfdev; struct usb_hcd *hcd = bus_to_hcd(hdev->bus); unsigned wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics); struct usb_device *udev; int status, i; dev_dbg (hub_dev, "port %d, status %04x, change %04x, %s\n", port1, portstatus, portchange, portspeed (portstatus)); if (hub->has_indicators) { set_port_led(hub, port1, HUB_LED_AUTO); hub->indicator[port1-1] = INDICATOR_AUTO; } /* Try to resuscitate an existing device */ udev = hdev->children[port1-1]; if ((portstatus & USB_PORT_STAT_CONNECTION) && udev && udev->state != USB_STATE_NOTATTACHED) { usb_lock_device(udev); if (portstatus & USB_PORT_STAT_ENABLE) { status = 0; /* Nothing to do */ } else { status = -ENODEV; /* Don't resuscitate */ } usb_unlock_device(udev); if (status == 0) { clear_bit(port1, hub->change_bits); return; } } /* Disconnect any existing devices under this port */ if (udev) usb_disconnect(&hdev->children[port1-1]); clear_bit(port1, hub->change_bits); if (portchange & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE)) { status = hub_port_debounce(hub, port1); if (status < 0) { if (printk_ratelimit()) dev_err(hub_dev, "connect-debounce failed, " "port %d disabled\n", port1); portstatus &= ~USB_PORT_STAT_CONNECTION; } else { portstatus = status; } } /* Return now if debouncing failed or nothing is connected */ if (!(portstatus & USB_PORT_STAT_CONNECTION)) { /* maybe switch power back on (e.g. root hub was reset) */ if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 && !(portstatus & (1 << USB_PORT_FEAT_POWER))) set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); if (portstatus & USB_PORT_STAT_ENABLE) goto done; return; } 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); if (!udev) { dev_err (hub_dev, "couldn't allocate port %d usb_device\n", port1); goto done; } usb_set_device_state(udev, USB_STATE_POWERED); udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; else if ((hdev->parent == NULL) && (portstatus & (1 << USB_PORT_FEAT_SUPERSPEED))) udev->speed = USB_SPEED_SUPER; else udev->speed = USB_SPEED_UNKNOWN; /* * xHCI needs to issue an address device command later * in the hub_port_init sequence for SS/HS/FS/LS devices. */ if (!(hcd->driver->flags & HCD_USB3)) { /* set the address */ choose_address(udev); if (udev->devnum <= 0) { status = -ENOTCONN; /* Don't retry */ goto loop; } } /* reset (non-USB 3.0 devices) and get descriptor */ status = hub_port_init(hub, udev, port1, i); if (status < 0) goto loop; /* consecutive bus-powered hubs aren't reliable; they can * violate the voltage drop budget. if the new child has * a "powered" LED, users should notice we didn't enable it * (without reading syslog), even without per-port LEDs * on the parent. */ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB && udev->bus_mA <= 100) { u16 devstat; status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstat); if (status < 2) { dev_dbg(&udev->dev, "get status %d ?\n", status); goto loop_disable; } le16_to_cpus(&devstat); if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) { dev_err(&udev->dev, "can't connect bus-powered hub " "to this port\n"); if (hub->has_indicators) { hub->indicator[port1-1] = INDICATOR_AMBER_BLINK; schedule_delayed_work (&hub->leds, 0); } status = -ENOTCONN; /* Don't retry */ goto loop_disable; } } /* check for devices running slower than they could */ if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200 && udev->speed == USB_SPEED_FULL && highspeed_hubs != 0) check_highspeed (hub, udev, port1); /* Store the parent's children[] pointer. At this point * udev becomes globally accessible, although presumably * no one will look at it until hdev is unlocked. */ status = 0; /* We mustn't add new devices if the parent hub has * been disconnected; we would race with the * recursively_mark_NOTATTACHED() routine. */ spin_lock_irq(&device_state_lock); if (hdev->state == USB_STATE_NOTATTACHED) status = -ENOTCONN; else hdev->children[port1-1] = udev; spin_unlock_irq(&device_state_lock); /* Run it through the hoops (find a driver, etc) */ if (!status) { status = usb_new_device(udev); if (status) { spin_lock_irq(&device_state_lock); hdev->children[port1-1] = NULL; spin_unlock_irq(&device_state_lock); } } if (status) goto loop_disable; status = hub_power_remaining(hub); if (status) dev_dbg(hub_dev, "%dmA power budget left\n", status); return; }
可以看到在这个函数中对设备的插拔进行的了处理。具体调用的函数有usb_new_device 以及 usb_disconnect。
至此,我们就看完了 root hub 感应设备插拔的过程。
相关文章推荐
- mini2440 usb host device controller驱动分析(四)-----root hub驱动的分析(二)
- mini2440 I2C驱动的分析与学习(一)
- Usb设备驱动1:root hub 设备驱动安装
- Usb设备驱动3:root hub守护进程2
- 基于linux的mini2440触摸屏驱动分析
- mini2440 root_qtopia 文件系统启动过程分析
- mini2440 usb device controller 驱动的分析--gadget设备(二)---枚举
- mini2440 led驱动程序经典分析
- MINI2440 按键输入子系统 驱动及测试代码分析
- mini2440 root_qtopia 文件系统启动过程分析
- 基于mini2440的按键驱动分析与总结
- linux sd卡驱动分析,基于mini2440,sdio mmc sd卡驱动编写
- mini2440 I2C驱动的分析与学习(二)
- mini2440驱动分析之TouchScreen
- 基于 mini2440 电阻式触摸屏(四):mini2440触摸屏驱动分析
- mini2440驱动分析之LED
- mini2440驱动分析之LCD
- linux sd卡驱动分析,基于mini2440,sdio mmc sd卡驱动
- 基于 mini2440 电阻式触摸屏(四):mini2440触摸屏驱动分析
- mini2440驱动分析之LCD