现在的位置: 首页 > 综合 > 正文

mini2440 usb host device controller驱动分析(一)—host controller

2013年12月04日 ⁄ 综合 ⁄ 共 8088字 ⁄ 字号 评论关闭

这里接着开始分析usb host端的驱动。

对于USB来讲,host端的驱动比我们之前分析的gadget端的驱动要复杂的多。但是有个好处就是驱动的主体部分已经实现得很完善了,我们写自己的驱动时候需要完成的部分并不多。

下面是host端驱动的结构图。

其中,host controller driver对应于mini2440就是ochi-mini2440.c,host controller driver与硬件关系最密切,负责配置寄存器等等。hcd interface 就是hcd 提供的标准接口,使得usb core中的其他部分可以屏蔽掉 host controller driver的不同。usb core 中实现的部分是usb host驱动的主体,包括了 usb host driver , usb
hub driver、 power management 和 device management。再往上层是usmon 和 usb class drivers。其中 usb class drivers 是负责实现具体的功能的,像usb-skeleton.c 就是一个usb class drivers. usb class drivers 在host端负责具体的功能实现,同时向上层提供相应的文件的接口。  与device 部分的驱动类似,像zero.c就是一个device上的class driver,负责实现具体的功能。因为USB提供给我们的只是一个通信的接口,具体我们用usb来实现的功能是要通过host端和device端的class
driver来实现的。usbmon是用于调试的,可以抓包。

 

这里,我们先把几个概念说清楚。

1.关于usb root hub。在每个usb host controller上面都集成了一个usb root hub。可以说它是属于usb host controller的一部分。usb root hub 的功能是负责检测 设备的插拔,获取设备的descriptor等。在core中有专门的root hub的驱动。root hub 也被看成了一个 usb device。

2. 关于s3c2440中的usb host controller。在s3c2440的数据手册中 并没有详细的介绍host controller相关的寄存器,s3c2440的host controller可以实现的是OHCI通信标准。这里之所以没有介绍 host controller相关的寄存器,是因为host controller的设计就是符合OHCI标准的,所以,在数据手册中有这样一句话:The S3C2440A USB host controller
complies with OHCI Rev 1.0. Refer to Open Host Controller Interface Rev 1.0
specification for detailed information.

所以,对于OHCI标准的实现,在host/ohci-*中,这些软件实现都是通用的,就是因为硬件设计也是通用的。我们看到对于s3c2440还单独实现了s3c2410-ohci.c这个文件,对于这个文件,我们之后可以看到其实并没有完成什么实质性的功能,都是调用ohci的通用函数实现的。在s3c2410-ohci.c中仅仅是进行了封装。

3. 关于OHCI EHCI  UCHI这几个标准。这几个通信标准 反应到软件上其实就是 hcd的驱动,也就是对应图中的usb host controller driver。 而为了屏蔽这三种驱动的不同,所以才提供了HCD interface这一层。

4. 关于usb通信中的地址。usb是一种树状的总线,也需要有地址用来标识数据包发给哪个节点。在usb中, 每个device都有地址,这个地址是在枚举的过程中调用set address命令进行配置的。配置完之后,device会将这个地址写入到自己的func_address_reg中,这样只要device不拔出,地址就不会变化。

 

再说明几个最重要的结构体。这些结构体都是用在host端的,device端不用。下面省略了 struct 关键词

  1. usb_device  usb_device_driver   这两者分别代表usb设备,usb设备的驱动。内核中只实现了一个usb_device_driver,叫做generic driver。用于所有设备。代码量不多,设备SET interface 等就是在这个generic driver中实现的。

    2. usb_driver   usb_interface          usb driver就是针对usb_interface的驱动。前面提到过 usb_interface就代表了一个可以完成一个特定功能的设备,usb-skeleton.c 就实现了一个usb_driver。

    3.  usb_bus      hc_driver       usb_bus代表host controller,可以对应于s3c2440上面的硬件。 hc_driver 是host controller的驱动。ohci-s3c2410.c就是实现了一个hc_driver。

    4.  host_config  host_endpoint    这两个分别代表host端得到的device的configuration和endpoint。其实我觉得host_endpoint像是一个proxy,因为真正的endpoint在device端,但是host端通信的时候往往用host_endpoint作为通信的对端。因此,这里可以看做一个proxy。

 

我们就从s3c2410-ohci.c开始分析。

static const struct hc_driver ohci_s3c2410_hc_driver = {
	.description =		hcd_name,
	.product_desc =		"S3C24XX OHCI",
	.hcd_priv_size =	sizeof(struct ohci_hcd),

	/*
	 * generic hardware linkage
	 */
	.irq =			ohci_irq,
	.flags =		HCD_USB11 | HCD_MEMORY,

	/*
	 * basic lifecycle operations
	 */
	.start =		ohci_s3c2410_start,
	.stop =		ohci_stop,
	.shutdown =		ohci_shutdown,

	/*
	 * managing i/o requests and associated device resources
	 */
	.urb_enqueue =		ohci_urb_enqueue,
	.urb_dequeue =		ohci_urb_dequeue,
	.endpoint_disable =	ohci_endpoint_disable,

	/*
	 * scheduling support
	 */
	.get_frame_number =	ohci_get_frame,

	/*
	 * root hub support
	 */
	.hub_status_data =	ohci_s3c2410_hub_status_data,
	.hub_control =		ohci_s3c2410_hub_control,
	.start_port_reset =	ohci_start_port_reset,
};

static int usb_hcd_s3c2410_probe (const struct hc_driver *driver,
				  struct platform_device *dev)
{
	struct usb_hcd *hcd = NULL;
	int retval;

	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->dev, "s3c24xx");
	if (hcd == NULL)
		return -ENOMEM;

	hcd->rsrc_start = dev->resource[0].start;
	hcd->rsrc_len   = dev->resource[0].end - dev->resource[0].start + 1;

	if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
		dev_err(&dev->dev, "request_mem_region failed\n");
		retval = -EBUSY;
		goto err_put;
	}

	clk = clk_get(&dev->dev, "usb-host");
	if (IS_ERR(clk)) {
		dev_err(&dev->dev, "cannot get usb-host clock\n");
		retval = -ENOENT;
		goto err_mem;
	}

	usb_clk = clk_get(&dev->dev, "usb-bus-host");
	if (IS_ERR(usb_clk)) {
		dev_err(&dev->dev, "cannot get usb-bus-host clock\n");
		retval = -ENOENT;
		goto err_clk;
	}

	s3c2410_start_hc(dev, hcd);

	hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
	if (!hcd->regs) {
		dev_err(&dev->dev, "ioremap failed\n");
		retval = -ENOMEM;
		goto err_ioremap;
	}

	ohci_hcd_init(hcd_to_ohci(hcd));

	retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED);
	if (retval != 0)
		goto err_ioremap;

	return 0;

}

首先,udc也是作为一个platform device的。因此,我们直接看它的probe函数。可以看到在probe函数中主要是调用了usb_create_hcd 和 usb_add_hcd。这两个函数的功能可以从函数名上猜测。其中,usb_add_hcd的第二个参数是一个中断号,后面我面再详细的看usb_add_hcd。我们看到这个ucd相关的驱动就是ohci_s3c2410_hc_driver。

此外,还通过s3c2410_start_hc用于启动host controller。我们看这个小函数。

static void s3c2410_start_hc(struct platform_device *dev, struct usb_hcd *hcd)
{
	struct s3c2410_hcd_info *info = dev->dev.platform_data;

	dev_dbg(&dev->dev, "s3c2410_start_hc:\n");  // info = NULL  for mini2440

	clk_enable(usb_clk);
	mdelay(2);			/* let the bus clock stabilise */

	clk_enable(clk);

	if (info != NULL) {
		info->hcd	= hcd;
		info->report_oc = s3c2410_hcd_oc;

		if (info->enable_oc != NULL) {
			(info->enable_oc)(info, 1);
		}
	}
}

对于s3c2410,info是NULL。因此这个函数的功能就是使能usb的clock。clock对于大部分用于通信功能的外设都是很重要的。使能了clk,host controller也就可以工作。

 

下面接着看重要的usb_add_hcd函数。

 

int usb_add_hcd(struct usb_hcd *hcd,
		unsigned int irqnum, unsigned long irqflags)
{
	int retval;
	struct usb_device *rhdev;

	dev_info(hcd->self.controller, "%s\n", hcd->product_desc);

	hcd->authorized_default = hcd->wireless? 0 : 1;
	set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);

	/* HC is in reset state, but accessible.  Now do the one-time init,
	 * bottom up so that hcds can customize the root hubs before khubd
	 * starts talking to them.  (Note, bus id is assigned early too.)
	 */
	if ((retval = hcd_buffer_create(hcd)) != 0) {
		dev_dbg(hcd->self.controller, "pool alloc failed\n");
		return retval;
	}

	if ((retval = usb_register_bus(&hcd->self)) < 0)
		goto err_register_bus;

	if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {
		dev_err(hcd->self.controller, "unable to allocate root hub\n");
		retval = -ENOMEM;
		goto err_allocate_root_hub;
	}

	switch (hcd->driver->flags & HCD_MASK) {
	case HCD_USB11:
		rhdev->speed = USB_SPEED_FULL;
		break;
	case HCD_USB2:
		rhdev->speed = USB_SPEED_HIGH;
		break;
	case HCD_USB3:
		rhdev->speed = USB_SPEED_SUPER;
		break;
	default:
		goto err_allocate_root_hub;
	}
	hcd->self.root_hub = rhdev;

	/* wakeup flag init defaults to "everything works" for root hubs,
	 * but drivers can override it in reset() if needed, along with
	 * recording the overall controller's system wakeup capability.
	 */
	device_init_wakeup(&rhdev->dev, 1);

	/* "reset" is misnamed; its role is now one-time init. the controller
	 * should already have been reset (and boot firmware kicked off etc).
	 */
	if (hcd->driver->reset && (retval = hcd->driver->reset(hcd)) < 0) {
		dev_err(hcd->self.controller, "can't setup\n");
		goto err_hcd_driver_setup;
	}

	/* NOTE: root hub and controller capabilities may not be the same */
	if (device_can_wakeup(hcd->self.controller)
			&& device_can_wakeup(&hcd->self.root_hub->dev))
		dev_dbg(hcd->self.controller, "supports USB remote wakeup\n");

	/* enable irqs just before we start the controller */
	if (hcd->driver->irq) {

		/* IRQF_DISABLED doesn't work as advertised when used together
		 * with IRQF_SHARED. As usb_hcd_irq() will always disable
		 * interrupts we can remove it here.
		 */
		if (irqflags & IRQF_SHARED)
			irqflags &= ~IRQF_DISABLED;

		snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",
				hcd->driver->description, hcd->self.busnum);
		if ((retval = request_irq(irqnum, &usb_hcd_irq, irqflags,
				hcd->irq_descr, hcd)) != 0) {
		}
		hcd->irq = irqnum;
	}
	if ((retval = hcd->driver->start(hcd)) < 0) {
		dev_err(hcd->self.controller, "startup error %d\n", retval);
		goto err_hcd_driver_start;
	}

	/* starting here, usbcore will pay attention to this root hub */
	rhdev->bus_mA = min(500u, hcd->power_budget);
	if ((retval = register_root_hub(hcd)) != 0)
		goto err_register_root_hub;

	retval = sysfs_create_group(&rhdev->dev.kobj, &usb_bus_attr_group);
	if (retval < 0) {
		printk(KERN_ERR "Cannot register USB bus sysfs attributes: %d\n",
		       retval);
		goto error_create_attr_group;
	}
	if (hcd->uses_new_polling && hcd->poll_rh)
		usb_hcd_poll_rh_status(hcd);
	return retval;

} 

 上面的程序我觉得主要完成三部分功能,一部分是完成device fs 相关的功能,将device反映到sysfs中。另外,是申请中断request_irq。后面看到usb_hcd_irq相当于是hcd的入口,因为初始化完成之后,许多工作都是由这个函数启动的(像检测设备的插拔、接收发送urb等)。最后,是对register_root_hub。将root hub进行注册。

另外,在上面调用了hcd->driver->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_init和ohci_run。

因为ohci_s3c2410.c就是实现了一个struct hc_driver ohci_s3c2410_hc_driver。接下来,我们在看看这个hc_driver的其他函数的作用。

其中irq 函数在usb_hcd_irq中被调用,usb_hcd_irq是一个wrapper。usb_enqueue dequeue用于实现IO操作。hub_status_data 和 hub_control分别用于检测hub的状态和控制hub。

到此,我们就把ohci_s3c2410.c部分的函数看完了。

 

 

 

 

 

 

抱歉!评论已关闭.