usb接口有大,小口之分
usb有主机(host,大口)/设备(otg, 小口)工作模式
usb主机可以主动去操作设备, 主机接口由4根线(vcc, gnd, d+, d-)
usb设备被主机操作, 有5根线(vcc, gnd, d+, d-, id), id线可用于区分设备接口
// otg接口即可作主机角色,也可以作设备的角色
usb接口可以像树型一样的进行扩展, 可用usb hub芯片(FE1.1S, GL850G)将一个usb的硬件接口分成4个硬件接口。
usb设备会在d+, d-这两根上设一个上拉, 当接到usb主机时,usb主机的d+/d-的电平会发生变化
usb的接口协议版本:
usb1.0
usb1.1
usb2.0
usb3.0
usb是高度标准化的接口, usb控制器与usb设备都会有一套标准规范, 以便两者可以互相兼容.
usb设备一般情况下都会有固件, 协同usb控制器的操作.所以有可能在usb设备接入后,需要设备驱动传输固件到设备里执行
/
usb主机控制器(host)和usb设备控制器(otg)的驱动通常由soc厂商负责驱动好, 我们负责的是设备驱动
usb core里面制定好usb主机控制器的驱动接口的实现, 和设备驱动调用控制器驱动的接口
usb主机控制器的版本:
uhci: usb1.x
ohci: usb1.1 (低速1.5Mbps, 全速12Mbps)
ehci: usb2.0 (480Mbps)
xhci: usb3.0
/
查看当前系统下有哪些usb设备及相关的信息:
可用lsusb命令查看, lsusb -vt
// 输出的内容里: bus表示控制器, port表示接在控制上的设备
当一个USB设备没有相应的设备驱动,接上系统时也会知道具体是什么设备,原因:
usb设备里会存放有idVendor, idProduct等信息, 当接入系统时, 内核里的usb子系统会调用usb主机控制器驱动把usb设备的信息读出来, 再根据idVendor, idProduct信息来查找并匹配设备驱动
//在usb设备驱动模型里,不再需要我们描述硬件相关资源的设备. usb子系统会在usb设备接入时自动创建相关的设备信息.
//
/// usb otg驱动 /// 使用设备控制器让我们整个板子做一个USB设备的角色
选择otg驱动:
make menuconfig
Device Drivers --->
[*] USB support --->
<*> USB Gadget Support --->
<M> USB Gadget Drivers //选择这步后,才可以选择下面的选项
<M> Mass Storage Gadget //通过开发板的设备接口接上PC,会出现u盘(也就是开发板作U盘的角色)
<M> Serial Gadget //开发板作uart的角色
<M> Printer Gadget //作打印机角色
保存退出,重编内核并使用新内核镜像启动。
在内核源码的根目录:
make modules
make modules_install INSTALL_MOD_PATH=/板子网络文件系统的根目录
板子上操作:
cd /lib/modules/3.4.112/kernel/drivers/usb/gadget/
此目录下有: g_mass_storage.ko g_serial.ko g_webcam.ko
可用modinfo查看驱动模块的参数。
接上板子的usb的otg线,让板子mmcblk0p7作u盘
modprobe g_mass_storage file=/dev/mmcblk0p7 removable=1
echo 2 > /sys/devices/platform/sunxi_usb_udc/otg_role //让板子的otg接口作设备用.
usb主机接口的设备驱动
在linux内核里通常会有一个结构体来描述一个硬件设备的资源, pci和usb的设备不需我们来描述, usb子系统会在usb设备接入时自动创建对应的结构体信息, 所以我们只要实现设备的驱动就可以了
//usb的设备驱动由usb_driver对象来描述
struct usb_driver {
const char *name;
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf); //相当于前面的remove函数
const struct usb_device_id *id_table; //匹配用
...
};
//usb设备驱动与usb设备的匹配有三种选择:
struct usb_device_id {
__u16 match_flags; //用于记录用哪种方法匹配
__u16 idVendor; //指定按usb设备的id来匹配
__u16 idProduct;
...
__u8 bDeviceClass; //按设备类型来匹配
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bInterfaceClass; //按接口类型来匹配
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
...
};
struct usb_device 对象表示一个usb设备
一个USB设备里由配置,接口, 端点组成, 一个接口(interface)需要一个usb设备驱动
(endpoint)端点: 用于主机与设备间通信通道。 一个端点只能一个方向。 输入/输出端点
(interface)接口: 一个接口表示USB设备的一种功能, 如一个USB设备有多种功能,则会有多个接口。 端点属于某个接口
(config)配置: 用于设置在某一时刻时USB设备所用的功能,接口需要配置来实现功能。
设备通常有一个或多个配置.
配置常常有一个或多个接口
接口常常有一个或多个设置.
接口有零或多个端点.
///
usb的端点分成4种:
control: 通常用作配置设备或者读取设备信息
INTERRUPT: 中断端点传送小量的数据, 以固定的速率在每次 USB 主请求设备数据时. usb鼠标, 键盘
BULK: 块端点传送大量的数据.普遍在打印机, 存储器, 和网络设备上
ISOCHRONOUS: 同步端点也传送大量数据, 但是这个数据常常不被保证它完成. 这些端点用在可以
处理数据丢失的设备中, 并且更多依赖于保持持续的数据流. 实时数据收集, 例如音频和视频设备, 一直都使用这些端点
不同类型的设备使用的端点类型也可能不同,但每个USB设备都会有control端点.有些USB设备只有control端点
USB 端点在内核中使用结构 struct usb_host_endpoint 来描述. 这个结构包含真实的端
点信息在另一个结构中, 称为 struct usb_endpoint_descriptor
struct usb_endpoint_descriptor {
....
__u8 bEndpointAddress; //端点地址, 位掩码USB_DIR_OUT 和 USB_DIR_IN 可用来和这个成员比对
__u8 bmAttributes; //这是端点的类型. 位掩码 USB_ENDPOINT_XFERTYPE_MASK 应当用来和这个值比对,来决定这个端点是否是 USB_ENDPOINT_XFER_ISOC, USB_ENDPOINT_XFER_BULK, 或者是类型 USB_ENDPOINT_XFER_INT
__le16 wMaxPacketSize; //以字节计的这个端点可一次处理的最大大小
__u8 bInterval; //如果这个端点是中断类型的, 这个值是为这个端点设置的间隔, 这个值以毫秒表示
...
} __attribute__ ((packed));
/////////////////////
USB 接口在内核中使用 struct usb_interface 结构来描述
struct usb_interface {
struct usb_host_interface *altsetting; //包含所有预备设置的接口结构的数组
struct usb_host_interface *cur_altsetting; //指向数组 altsetting 的一个指针, 表示这个接口当前的激活的设置
unsigned num_altsetting; //由 altsetting 指针指向数组的元素个数
int minor;
...
};
///////////
struct usb_host_interface {
struct usb_interface_descriptor desc; // desc.bNumEndpoints 表示端点个数
struct usb_host_endpoint *endpoint; //端点数组首地址, endpoint[i].desc可取到struct usb_endpoint_descriptor对象的信息
};
linux 描述 USB 配置使用结构 struct usb_host_config 和整个 USB 设备使用结构
struct usb_device. USB 设备驱动通常不会需要读写这些结构的任何值, 因此它们在这
里没有详细定义
一个 USB 设备驱动通常不得不转换数据从给定的 struct usb_interface 结构到 struct
usb_device 结构, USB 核心需要给很多的函数调用. 为此, 提供有函数
interface_to_usbdev
实验获取usb设备的信息:
去掉内核的usb鼠标,键盘,蓝牙鼠标等的驱动
make menuconfig
Device Drivers --->
[ ] HID Devices --->
保存后,重编内核,并使用新镜像启动系统。
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
int myprobe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_host_interface *hintf = intf->cur_altsetting;
int n, i;
struct usb_host_endpoint *endp;
printk("num_alt = %d\n", intf->num_altsetting);
n = hintf->desc.bNumEndpoints;
endp = hintf->endpoint;
printk("endpoints = %d\n", n);
for (i = 0; i < n; i++)
{
printk("endpoint address: %x\n", endp[i].desc.bEndpointAddress);
printk("direction : ");
if (endp[i].desc.bEndpointAddress & USB_DIR_IN)
printk("input\n");
else
printk("output\n");
printk("type : ");
switch (endp[i].desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
{
case USB_ENDPOINT_XFER_ISOC:
printk("isoc \n");
break;
case USB_ENDPOINT_XFER_BULK:
printk("bulk \n");
break;
case USB_ENDPOINT_XFER_INT:
printk("int interval = %d\n", endp[i].desc.bInterval);
break;
case USB_ENDPOINT_XFER_CONTROL:
printk("control \n");
break;
}
printk("maxpacketSize = %d\n", endp[i].desc.wMaxPacketSize);
}
printk("in %s, %d\n", __func__, __LINE__);
return 0;
}
void mydisconnect(struct usb_interface *intf)
{
printk("in %s, %d\n", __func__, __LINE__);
}
struct usb_device_id ids[] = {
{USB_DEVICE(0x046d, 0xc05a)}, // idVendor=046d, idProduct=c05a mouse
{USB_DEVICE(0x413c, 0x2107)}, // idVendor=413c, idProduct=2107 keyboard
{USB_DEVICE(0x0951, 0x1666)}, // idVendor=0951, idProduct=1666 u_disk
{},
};
struct usb_driver myusb = {
.name = "myusb",
.probe = myprobe,
.disconnect = mydisconnect,
.id_table = ids,
};
module_usb_driver(myusb);
MODULE_LICENSE("GPL");
///处理usb设备的数据/
linux 内核中的 USB 代码和所有的 USB 设备通讯使用称为 urb 的东西( USB request block)
也就是说所有USB设备要通过USB控制器收发数据需使用urb
1)被一个 USB 设备驱动创建.
2)安排给一个特定 USB 设备的特定端点.
3)提交给 USB 核心, 被 USB 设备驱动.
4)提交给特定设备的被 USB 核心指定的 USB 主机控制器驱动, . // usb_submit_urb(urb, GFP_ATOMIC);
5)被 USB 主机控制器处理, 它做一个 USB 传送到设备.
6)当 urb 完成, USB 主机控制器驱动通知 USB 设备驱动.
struct urb {
...
struct usb_device *dev; //指向这个 urb 要发送到的 struct usb_device 的指针
unsigned int pipe; //用于与设备端点通信的通道。不同类型的端点需要用不同的函数来的创建:
// usb_sndctrlpipe(...), usb_rcvctrlpipe, usb_sndbulkpipe, usb_rcvbulkpipe, usb_sndintpipe, usb_rcvintpipe
...
void *transfer_buffer; //用于存放发送数据到设备(对一个 OUT urb)或者从设备中获取数据(对于一个 IN
urb)的缓冲区地址
u32 transfer_buffer_length; //数据缓冲区的大小
u32 actual_length; //当urb完成后, 在数据缓冲区里实际存放数据的大小
...
int interval; //中断端点设备的轮询间隔时间
...
void *context; //存放设备数据的缓冲区, 像dev_set_drvdata(...)这样用的数据缓冲区
usb_complete_t complete; //urb传输完成后, 触发的处理函数的地址。函数原型void (*complete)(struct urb *urb).
...
};
创建和销毁 urb的函数:
struct urb *usb_alloc_urb(int iso_packets, int mem_flags); //当不是同步端点时,iso_packets为0. mem_flags通常设为GFP_KERNEL.
void usb_free_urb(struct urb *urb);
urb结构体成员较多,内核提供一些函数以方便初始化urb:
//中断端点:
void usb_fill_int_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, void *transfer_buffer,
int buffer_length, usb_complete_t complete,
void *context, int interval);
//块端点:
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, void *transfer_buffer,
int buffer_length, usb_complete_t complete,
void *context);
//控制端点:
void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, unsigned char *setup_packet,
void *transfer_buffer, int buffer_length,
usb_complete_t complete, void *context);
//同步端点没有直接调用函数,只能一个成员一个成员来初始化:
urb->dev = dev;
urb->context = uvd;
urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);
urb->interval = 1;
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = cam->sts_buf[i];
urb->complete = konicawc_isoc_irq;
urb->number_of_packets = FRAMES_PER_DESC;
urb->transfer_buffer_length = FRAMES_PER_DESC;
提交urb, 即让usb控制来传输数据, 当完成后就调用urb里的complete函数.
int usb_submit_urb(struct urb *urb, int mem_flags); // mem_flags通常设为GFP_ATOMIC
//
实验,获取中断端点设备的数据:
test.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/slab.h>
struct urb *myurb;
void mycomplete(struct urb *urb)
{
int i;
unsigned char *data = urb->context;
for (i = 0; i < urb->actual_length; i++)
{
printk("%02x ", data[i]);
}
printk("\n");
usb_submit_urb(myurb, GFP_ATOMIC);
}
int myprobe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_host_interface *hintf = intf->cur_altsetting;
int n, i;
struct usb_host_endpoint *endp;
unsigned int pipe;
struct usb_device *dev;
unsigned char *buffer;
printk("num_alt = %d\n", intf->num_altsetting);
n = hintf->desc.bNumEndpoints;
endp = hintf->endpoint;
printk("endpoints = %d\n", n);
//
myurb = usb_alloc_urb(0, GFP_KERNEL);
dev = interface_to_usbdev(intf);
pipe = usb_rcvintpipe(dev, endp[0].desc.bEndpointAddress);
buffer = kzalloc(endp[0].desc.wMaxPacketSize, GFP_KERNEL);
usb_fill_int_urb(myurb, dev, pipe, buffer, endp[0].desc.wMaxPacketSize,
mycomplete, buffer, endp[0].desc.bInterval);
printk("in %s, %d\n", __func__, __LINE__);
usb_submit_urb(myurb, GFP_ATOMIC);
return 0;
}
void mydisconnect(struct usb_interface *intf)
{
usb_free_urb(myurb);
printk("in %s, %d\n", __func__, __LINE__);
}
struct usb_device_id ids[] = {
{USB_DEVICE(0x046d, 0xc05a)}, // idVendor=046d, idProduct=c05a mouse
{USB_DEVICE(0x413c, 0x2107)}, // idVendor=413c, idProduct=2107 keyboard
{USB_DEVICE(0x0951, 0x1666)}, // idVendor=0951, idProduct=1666 u_disk
{},
};
struct usb_driver myusb = {
.name = "myusb",
.probe = myprobe,
.disconnect = mydisconnect,
.id_table = ids,
};
module_usb_driver(myusb);
MODULE_LICENSE("GPL");
另:通常情况下,设备驱动里都是通过urb来获取设备的数据。但有些说法,说不用urb,而通过一些函数来完成工作。
//下面两个函数用于简单的传输数据, 函数内部其实还是创建urb, 提交urb.
usb_bulk_msg 创建一个 USB 块 urb 并且发送它到特定的设备, 接着在返回到调用者之
前等待完成. 它定义为:
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length,
int timeout);
usb_control_msg 函数就像 usb_bulk_msg 函数, 除了它允许一个驱动发送和结束 USB
控制信息:
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8
requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);