分类: LINUX linux usb hub驱动 谨以此文纪念过往的岁月 一.前言 OHCI (Open Host Controller Interface),是康柏、微软、国家半导体等公司共同制定的一个USB主机接口规范,它提供一个更抽象的接口来完成USB数据传输工作。 在OHCI规范中,最重要的几个概念是端点(EndPoint - ED)、传输描述符(Transport Descriptor - TD)、主机控制器通信区(HCCA)。其中ED负责确定传输类型(控制传输、批量传输、同步传输和中断传输)。TD确定传输参数。HCCA用于确定数据 传输是否完毕。 本文针对s3c6410的usb host的驱动来讲述usb host的驱动。 二.usb host驱动 话说s3c6410的硬件支持ohci,其配置时也是采用的是ohci-hcd,其编译时为ohci-hcd.c文件。 static int __init ohci_hcd_mod_init(void) { } 其实真正注册的是ohci_hcd_s3c2410_driver这个驱动。那我们来看一下这个结构体的具体值。 static struct platform_driver ohci_hcd_s3c2410_driver = { .probe= ohci_hcd_s3c2410_drv_probe, .remove= ohci_hcd_s3c2410_drv_remove, .shutdown= usb_hcd_platform_shutdown, .driver= { .owner= THIS_MODULE, .name= 's3c2410-ohci', }, }; 那我们一一来看上述的每一个函数的实现。 2.1 hcd 探测 函数很简单其实现功能的是usb_hcd_s3c2410_probe函数。 static int ohci_hcd_s3c2410_drv_probe(struct platform_device *pdev) { } ohci_s3c2410_hc_driver提供了对于ohci的操作集。对于这些函数在后面的学习中去看,在此不加扩展。我们将下面的函数剔除枝叶留其主干。 static int usb_hcd_s3c2410_probe (const struct hc_driver *driver, struct platform_device *dev) { struct usb_hcd *hcd = NULL; int retval; #if !defined(CONFIG_ARCH_2410) usb_host_clk_en();--使能clk #endif s3c2410_usb_set_power(dev->dev.platform_data, 1, 1); s3c2410_usb_set_power(dev->dev.platform_data, 2, 1); hcd = usb_create_hcd(driver, dev, 's3c24xx');--创建一个hcd hcd->rsrc_start = dev->resource[0].start; --获取物理地址 hcd->rsrc_len= dev->resource[0].end - dev->resource[0].start + 1; request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name); clk = clk_get(dev, 'usb-host'); s3c2410_start_hc(dev, hcd); hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); ohci_hcd_init(hcd_to_ohci(hcd)); return 0; } 对于usb的电源管理,我们暂时不看,不看不代表不重要,电源管理是很重要的。 那依次来看上面的函数。usb_create_hcd创建和初始化一个hcd结构体。 参数说明: Driver :将使用该hcd的hc驱动 dev:该hc的设备实体,将存储在hcd->self.controller中。 bus_name: 存储在hcd->self.bus_name的值 struct device *dev, const char *bus_name) { struct usb_hcd *hcd; hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL); if (!hcd) { return NULL; } dev_set_drvdata(dev, hcd);--将hcd存储到dev的私用数据。 kref_init(kref);--初始化hcd的引用计数 hcd->self.uses_dma = (dev->dma_mask != NULL); --使用使用dma的旗标 --如何理解该定时器,众所周知usb采用轮询的办法来实现对从设备的读取数据,那有一点就会很疑惑就是那所谓中断传输是怎么一回事,从字面意思去理解应该是从设备如果有数据发送,则产生一个中断通知usb host,这是与usb协议相互违背的。那这是怎么一回事呢,其实这就是timer的作用了。 init_timer(rh_timer); hcd->rh_timer.function = rh_timer_func; hcd->rh_timer.data = (unsigned long) hcd; hcd->driver = driver; hcd->product_desc = (driver->product_desc) ? driver->product_desc : 'USB Host Controller'; return hcd; } 上面就是初始化一个hcd的实体。 s3c2410_start_hc启动hc。这里有一个很奇怪的结构体就是struct s3c2410_hcd_info,在s3c6410中并没有看到该结构体的赋值。也许有人对此很困惑,该结构体做什么用的。那我们来看该结构体的真正面目。 struct s3c2410_hcd_info { struct usb_hcd*hcd;--保存该hcd_info所属的hcd struct s3c2410_hcd_portport[2]; --两个端口。 }; 在usb-host.txt中对其功能进行了说明,就是一对函数,使能过流检测和控制端口电源状态。 power_control:使能或禁止端口电源 enable_oc:使能或禁止端口过流检测 report_oc:当端口存在过流,则会调用该函数。 { struct s3c2410_hcd_info*info = dev->dev.platform_data; clk_enable(clk); if (info != NULL) {--在s3c6410中该info为空。 info->hcd= hcd; info->report_oc = s3c2410_hcd_oc; if (info->enable_oc != NULL) { (info->enable_oc)(info, 1); } } } 初始化ohci_hcd { ohci->next_statechange = jiffies; spin_lock_init (&lock); INIT_LIST_HEAD (&pending); } 初始化并注册usb_hcd 完成通用hcd的初始化和注册,在这里同时完成中断的申请和注册。 int usb_add_hcd(struct usb_hcd *hcd,unsigned int irqnum, unsigned long irqflags) { int retval; struct usb_device *rhdev; hcd->authorized_default = hcd->wireless? 0 : 1;--判断是否为无线 if ((retval = hcd_buffer_create(hcd)) != 0) { --开辟hcd的缓冲区 return retval; } if ((retval = usb_register_bus(self)) < 0) goto err_register_bus; if ((rhdev = usb_alloc_dev(NULL, self, 0)) == NULL) { retval = -ENOMEM; goto err_allocate_root_hub; } rhdev->speed = (hcd->driver->flags & HCD_USB2) ? USB_SPEED_HIGH :USB_SPEED_FULL; --指定根hub的speed hcd->self.root_hub = rhdev; device_init_wakeup(&rhdev->dev, 1); if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) {--为NULL goto err_hcd_driver_setup; } if (device_can_wakeup(hcd->self.controller) && device_can_wakeup(self.root_hub->dev)) dev_dbg(hcd->self.controller, 'supports USB remote wakeup\n'); if (hcd->driver->irq) {--中断处理 if (irqflags & IRQF_SHARED) irqflags &= ~IRQF_DISABLED; snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), '%s:usb%d', hcd->driver->description, hcd->self.busnum); request_irq(irqnum, &usb_hcd_irq, irqflags,hcd->irq_descr, hcd);--申请中断线 } hcd->irq = irqnum; } else { hcd->irq = -1; } hcd->driver->start(hcd);--调用start为ohci_s3c2410_start rhdev->bus_mA = min(500u, hcd->power_budget); register_root_hub(hcd)) ;--注册root hub retval = sysfs_create_group(&rhdev->dev.kobj, &usb_bus_attr_group); if (retval < 0) { goto error_create_attr_group; } if (hcd->uses_new_polling &&poll_rh) usb_hcd_poll_rh_status(hcd); return retval; } 那一一来看上面的函数,学习内核就要有打破砂锅问到底的精神,唯有知道那背后的种种风光,才能领略那种种风采。闲话不说,继续! 记住下面结构体中flag的值。那就看这几个宏定义是什么意思。 #defineHCD_MEMORY0x0001--hc的寄存器使用memory映射 #defineHCD_LOCAL_MEM0x0002--hc使用local memory #defineHCD_USB110x0010--usb1.1 #defineHCD_USB20x0020--usb2.0 static const struct hc_driver ohci_s3c2410_hc_driver= { .flags =HCD_USB11 | HCD_MEMORY, }; 为hcd分配缓冲池,当hc需要使用DMA内存分配器。 int hcd_buffer_create(struct usb_hcd *hcd) { charname[16]; int i, size; if (!hcd->self.controller->dma_mask && !(hcd->driver->flags & HCD_LOCAL_MEM)) return 0; --#define HCD_BUFFER_POOLS 4 我们查看pool_max其实是一个全局数组。如果需要开辟的缓冲区更大的话,直接采用分配page的函数。 static const size_tpool_max [HCD_BUFFER_POOLS] = { 32,128,512,PAGE_SIZE / 2 }; if (!size) continue; snprintf(name, sizeof name, 'buffer-%d', size); if (!hcd->pool [i]) { hcd_buffer_destroy(hcd); return -ENOMEM; } } return 0; } 下面的函数是usb_bus注册,对于该函数也许很难理解。不过参照网上http://www./info/html/edu/20080425/301909.html的说明,估计会好理解很多。 每个主机控制器拥有一个USB系统,称为一个USB总线。USBD支持多个主机控制器,即多个USB总线。当每增加一个主机控制器时,会给他分配一个usb_bus结构。USBD动态安装和卸载主机驱动。主机驱动安装时,他的初始化函数一方面完成主机控制器硬件的设置和初始化工作,另一方面调用usb_alloc_bus和usb_register_bus来将自己注册到USBD中去,供USB子系统访问。 static int usb_register_bus(struct usb_bus *bus) { int busnum; mutex_lock(&usb_bus_list_lock); --用busmap来存储主机驱动,一个bit位代表一个主机驱动 if (busnum >= USB_MAXBUS) { return result; } set_bit (busnum, busmap.busmap); bus->busnum = busnum; bus->dev = device_create(usb_host_class, bus->controller, MKDEV(0, 0),bus, 'usb_host%d', busnum); --在usb_host类下创建一个usb_host设备。 list_add (bus_list, &usb_bus_list); mutex_unlock(&usb_bus_list_lock); usb_notify_add_bus(bus); return 0; } 那大家也许会好奇该bus有什么作用。那下面就看usb_bus的真实面目。要知道爱一个人,不仅仅是爱她的外表还有内在,真正的她。 struct usb_bus { struct device *controller;--主机的硬件 int busnum;--bus number const char *bus_name;--bus name u8 uses_dma;--主机控制器是否使用DMA u8 otg_port;--otg的端口 unsigned is_b_host:1; unsigned b_hnp_enable:1; int devnum_next;--在循环开辟中下一个打开的设备 struct usb_devmap devmap;--设备地址分配匹配。 struct usb_device *root_hub;--根hub struct list_head bus_list;--buses 链表 int bandwidth_allocated;--90%,而高速保留80%在这个总线上,每个周期多少时间被保留使用。单位:微秒/帧。全速和低速设备保留 int bandwidth_int_reqs;--中断请求个数 int bandwidth_isoc_reqs;--等时请求个数 #ifdef CONFIG_USB_DEVICEFS struct dentry *usbfs_dentry;--bus的usbfs目录入口 #endif struct device *dev;--该bus设备 }; 可以同过usb_register_bus来注册一个usb host总线,当然也可以通过usb_deregister_bus来注销一个usb host的总线。 注册bus过后来为usb host分配一个usb_device.记住传入的参数parent为NULL,因为该usb hub为根hub。 struct usb_device *usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1) { struct usb_device *dev; struct usb_hcd *usb_hcd = container_of(bus, struct usb_hcd, self); unsigned root_hub = 0; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return NULL; if (!usb_get_hcd(bus_to_hcd(bus))) { kfree(dev); return NULL; } device_initialize(dev); dev->dev.bus = &usb_bus_type; dev->dev.type = &usb_device_type; dev->dev.groups = usb_device_groups; dev->dev.dma_mask = bus->controller->dma_mask; set_dev_node(dev, dev_to_node(bus->controller)); dev->state = USB_STATE_ATTACHED;--该hub的状态为以连接 atomic_set(urbnum, 0); INIT_LIST_HEAD(ep0.urb_list); dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE; dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT; usb_enable_endpoint(dev, ep0, true); dev->can_submit = 1; if (unlikely(!parent)) { dev->devpath[0] = '0'; dev->dev.parent = bus->controller; dev_set_name(dev, 'usb%d', bus->busnum); root_hub = 1; } else { if (parent->devpath[0] == '0') snprintf(dev->devpath, sizeof dev->devpath, '%d', port1); else snprintf(dev->devpath, sizeof dev->devpath, '%s.%d', parent->devpath, port1); dev->dev.parent = &parent->dev; dev_set_name(dev, '%d-%s', bus->busnum, dev->devpath); } dev->portnum = port1; dev->bus = bus; dev->parent = parent; INIT_LIST_HEAD(filelist); if (root_hub)/* Root hub always ok [and always wired] */ dev->authorized = 1; else { dev->authorized = usb_hcd->authorized_default; dev->wusb = usb_bus_is_wusb(bus)? 1 : 0; } return dev; } ohci_s3c2410_start这个函数很假,其实他自己没有做什么。 static int ohci_s3c2410_start (struct usb_hcd *hcd) { struct ohci_hcd*ohci = hcd_to_ohci (hcd); int ret; if ((ret = ohci_init(ohci)) < 0) return ret; if ((ret = ohci_run (ohci)) < 0) { err ('can't start %s', hcd->self.bus_name); ohci_stop (hcd); return ret; } return 0; } 完成硬件OHCI的初始化和运行。关于OHCI的硬件初始化,以后再专门来看。现在主要看root hub的软件注册。 注册root hub,将root hub注册到usb子系统中。通过usb_new_device来注册一个usb device,同时为root hub分配地址,通常是1。 static int register_root_hub(struct usb_hcd *hcd) { struct device *parent_dev = hcd->self.controller; struct usb_device *usb_dev = hcd->self.root_hub; const int devnum = 1; int retval; usb_dev->devnum = devnum; usb_dev->bus->devnum_next = devnum + 1; memset (&usb_dev->bus->devmap.devicemap, 0, sizeof usb_dev->bus->devmap.devicemap); set_bit (devnum, usb_dev->bus->devmap.devicemap); usb_set_device_state(usb_dev, USB_STATE_ADDRESS); mutex_lock(&usb_bus_list_lock); usb_dev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64); retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE); if (retval != sizeof usb_dev->descriptor) { mutex_unlock(&usb_bus_list_lock); return (retval < 0) ? retval : -EMSGSIZE; } retval = usb_new_device (usb_dev); if (retval) { } mutex_unlock(&usb_bus_list_lock); if (retval == 0) { spin_lock_irq (&hcd_root_hub_lock); hcd->rh_registered = 1; spin_unlock_irq (&hcd_root_hub_lock); if (hcd->state == HC_STATE_HALT) usb_hc_died (hcd);/* This time clean up */ } return retval; } 到此usb_hcd_s3c2410_probe就算是看完了,不过还是很粗糙。继续! 三.总结 在该次学习中主要是了解一个usb主机控制器的加载。记住不是所有的hub都是root hub,对于usb子系统而言,只有唯一的root hub。hcd host controller driver其意为主机控制器驱动。对于普通的hub的而言,其仅仅是root hub下挂载的一个设备。不过对于hub的驱动和普通的usb设备驱动是不同的。在linux中会为每一个hub创建一个usb_bus。 阅读(3241) | 评论(1) | 转发(2) |
|
|