一:Linux USB驱动介绍

1:USB驱动简介

USB采用树形拓扑结构,主机侧和设备侧的USB控制器分别称为主机控制器(Host)和USB设备控制器(UDC),每条总线上只要一个主机控制器,负责协调主机和设备间的通讯,而设备不能主动向主机发送任何信息。

从主机侧观察,在Linux驱动中,处于USB驱动的最底层的时USB主机控制器硬件,在其上运行的时USB主机控制器驱动,在主机控制器上的为USB核心层,再上层为USB设备驱动层(U盘,鼠标,键盘,USB转串口等设备驱动)。因此,在主机侧的层次结构中,要实现的USB驱动包括两类:USB主机控制器驱动和USB设备驱动,前者控制插入其中的USB设备,后者控制USB设备如何与主机通讯。Linux内核中的USB核心负责USB驱动管理和协议处理的主要工作。主机控制器驱动和设备驱动之间的USB核心非常重要,其功能包括:通过定义一些数据结构,宏和功能函数,向上为设备驱动提供编程接口,向下为USB主机控制器驱动提供编程接口;维护整个系统的USB设备信息;完成设备热插拔控制,总线数据传输控制等。

USB设备热插拔的硬件原理:

在USB集线器(hub)的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。

USB主机控制器接口:

OHCI(Open Host Controller Interface): 微软主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps),OHCI接口的软件简单,硬件复杂 UHCI(Universal Host Controller Interface): Intel主导的低速USB1.0(1.5Mbps)和全速USB1.1(12Mbps), UHCI接口的软件复杂,硬件简单EHCI(Enhanced Host Controller Interface):高速USB2.0(480Mbps),                                                                                        xHCI(eXtensible Host Controller Interface):USB3.0(5.0Gbps),采用了9针脚设计,同时也支持USB2.0、1.1等

USB的4种传输类型:

(1)控制传输(control):是每一个USB设备必须支持的,通常用来获取设备描述符、设置设备的状态等等。一个USB设备从插入到最后的拔出这个过程一定会产生控制传输(即便这个USB设备不能被这个系统支持)。

(2)中断传输(interrupt):支持中断传输的典型设备有USB鼠标、 USB键盘等等。中断传输不是说我的设备真正发出一个中断,然后主机会来读取数据。它其实是一种轮询的方式来完成数据的通信。USB设备会在设备驱动程序中设置一个参数叫做interval,它是endpoint的一个成员。 interval是间隔时间的意思,表示我这个设备希望主机多长时间来轮询自己,只要这个值确定了之后,我主机就会周期性的来查看有没有数据需要处理

(3)批量传输(bulk):支持批量传输最典型的设备就是U盘,它进行大数量的数据传输,能够保证数据的准确性,但是时间不是固定的。

(4)实时传输(isochronous) :USB摄像头就是实时传输设备的典型代表,它同样进行大数量的数据传输,数据的准确性无法保证,但是对传输延迟非常敏感,也就是说对实时性要求比较高

2:设备、配置、接口、端点

在USB设备的逻辑组织中,包含设备,配置,接口和端点4个层次。每个USB设备都提供不同级别的设备信息,可以包含一个或多个配置,不同的配置使设备表现出不同的功能组合,配置由多个接口组成,在USB协议中,接口由多个端点组成,代表一个基本的功能,是USB设备驱动程序控制的对象,一个功能复杂的USB设备可以具有多个接口。每个配置中可以有多个接口,而设备接口是端点的汇集。

端点是USB通信的最基本形式,每一个USB设备接口在主机看来就是一个端点的集合。主机只能通过端点与设备进行通信,以使用设备的功能。在USB系统中每一个的端点都有唯一的地址,这是由设备地址和端点号给出的。每个端点都有一定的属性,其中包括传输方式,总线访问频率,带宽,端点号和数据包的最大容量等。一个USB端点只能在一个方向上承载数据,从主机到设备(输出端点)或者从设备到主机(输入端点),因此端点可以看作是一个单向的管道。端点0通常为控制端点,用于设备初始化参数能。只要设备连接到USB上并且上电,端点0就可以被访问。端点1,2等一般作用数据端点,存放主机与设备间往来的数据。

  • 设备通常有一个或多个配置;
  • 配置通常有一个或多个接口;
  • 接口通常有一个或多个设置;
  • 接口有零个或多个端点。

二:USB设备框架分析

1:usb_hub初始化时创建hub线程并进入等待队列khubd_wait等待唤醒

int usb_hub_init(void)
{... ...khubd_task = kthread_run(hub_thread, NULL, "khubd");if (!IS_ERR(khubd_task))return 0;... ...
}static int hub_thread(void *__unused)
{... ...hub_events();wait_event_interruptible(khubd_wait,        //进入等待唤醒队列!list_empty(&hub_event_list) ||kthread_should_stop());... ...
}

2:插入USB设备后,D+或者D-会被拉高,USB主机控制器会产生一个hub_irq中断并唤醒等待队列khubd_wait

static void hub_irq(struct urb *urb)
{... .../* Something happened, let khubd figure it out */kick_khubd(hub);... ...
}static void kick_khubd(struct usb_hub *hub)
{... ...wake_up(&khubd_wait);... ...
}

3:hub线程中的hub事件分析

static void hub_events(void)
{... ... if (connect_change)hub_port_connect_change(hub, i,portstatus, portchange);... ...
}

4:hub_port_connect_change分析

static void hub_port_connect_change(struct usb_hub *hub, int port1,u16 portstatus, u16 portchange)
{ ... ...udev = usb_alloc_dev(hdev, hdev->bus, port1);     //(1)注册一个usb_device,然后会放在usb总线上choose_address(udev);                              //(2)给新的设备分配一个地址编号status = hub_port_init(hub, udev, port1, i);     //(3)初始化端口,与USB设备建立连接status = usb_new_device(udev);                 //(4)创建USB设备,与USB设备驱动连接... ...
}

usb_alloc_dev:分配usb_device并设置

struct usb_device *
usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1)
{struct usb_device *dev;              //分配usb_devicedev = kzalloc(sizeof(*dev), GFP_KERNEL);... ...device_initialize(&dev->dev);        //初始化dev->dev.bus = &usb_bus_type;        //设置bus成员dev->dev.type = &usb_device_type;    //设置device成员... ...return dev;                          //返回usb_device
}struct bus_type usb_bus_type = {         // bus总线,跟platform平台总线相似.name =      "usb",.match =   usb_device_match,.uevent = usb_uevent,.suspend =  usb_suspend,.resume =  usb_resume,
};struct device_type usb_device_type = {.name =       "usb_device",.release =  usb_release_dev,
};

choose_address:选择地址,在devnum_next和128之间寻找一个空设备编号

static void choose_address(struct usb_device *udev)
{int        devnum;struct usb_bus   *bus = udev->bus;/* If khubd ever becomes multithreaded, this will need a lock *//* Try to allocate the next devnum beginning at bus->devnum_next. */devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);if (devnum >= 128)devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);if (devnum < 128) {set_bit(devnum, bus->devmap.devicemap);udev->devnum = devnum;}
}

hub_port_init:设置地址,获取设备描述符

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
{... ...for (j = 0; j < SET_ADDRESS_TRIES; ++j) {retval = hub_set_address(udev);if (retval >= 0)break;msleep(200);}... ...retval = usb_get_device_descriptor(udev, 8);... ...retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);}

usb_new_device:获取usb设备配置,将新创建的设备添加进dev链表中,寻求与之匹配的驱动

int usb_new_device(struct usb_device *udev)
{err = usb_get_configuration(udev);... ...err = device_add(&udev->dev);
}

三:USB驱动框架分析

        LInux系统实现了几类通用的USB设备驱动,可划分为以下几种:

  • 音频设备类
  • 通信设备类
  • HID(人机接口)设备类
  • 显示设备类
  • 海量存储设备类
  • 电源设备类
  • 打印设备类
  • 集线器设备类

usb总线跟platform总线,i2c等总线类似,在Linux内核中,使用usb_driver结构体描述一个usb设备驱动,其结构体定义如下:

struct usb_driver {const char *name;int (*probe) (struct usb_interface *intf,const struct usb_device_id *id);void (*disconnect) (struct usb_interface *intf);int (*ioctl) (struct usb_interface *intf, unsigned int code,void *buf);int (*suspend) (struct usb_interface *intf, pm_message_t message);int (*resume) (struct usb_interface *intf);void (*pre_reset) (struct usb_interface *intf);void (*post_reset) (struct usb_interface *intf);const struct usb_device_id *id_table;struct usb_dynids dynids;struct usbdrv_wrap drvwrap;unsigned int no_dynamic_id:1;unsigned int supports_autosuspend:1;
};

在编写新的USB设备驱动时,主要完成的是probe()和disconnect()函数,即探测和断开函数,它们分别在设备被插入和拔出的时候调用,用于初始化和释放软硬件资源。usb_driver的注册和注销函数如下:

int usb_register(struct usb_driver *driver);
void usb_deregister(struct usb_driver *driver);

usb_driver结构体中的id_table成员描述了这个USB驱动所支持的USB设备列表,它指向一个usb_device_id数组,usb_device_id结构体包含有USB设备的制造商ID,产品ID,产品版本,设备类,接口类等信息及其要匹配标志成员match_flags。可以借助下面一组宏生成usb_device_id结构体的实例:

(1) 该宏根据制造商ID和产品ID生成一个usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支持与制造商ID,产品ID匹配的设备。

#define USB_DEVICE(vend,prod) \.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = (vend), \.idProduct = (prod)

(2)该宏根据制造商ID,产品ID,产品版本的最小值和最大值生成一个usb_device_id结构体的实例,在数组中增加该元素将意味着该驱动可支持与制造商ID,产品ID匹配和lo~hi范围内版本的设备。

#define USB_DEVICE_VER(vend,prod,lo,hi) \.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \.idVendor = (vend), .idProduct = (prod), \.bcdDevice_lo = (lo), .bcdDevice_hi = (hi)

(3) 该宏用于创建一个匹配设备指定类型的usb_device_id结构体的实例。

#define USB_DEVICE_INFO(cl,sc,pr) \.match_flags = USB_DEVICE_ID_MATCH_DEV_INFO, .bDeviceClass = (cl), \.bDeviceSubClass = (sc), .bDeviceProtocol = (pr)

(4) 该宏用于创建一个匹配接口指定类型的usb_device_id结构体的实例。

#define USB_INTERFACE_INFO(cl,sc,pr) \.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, .bInterfaceClass = (cl), \.bInterfaceSubClass = (sc), .bInterfaceProtocol = (pr)

例如:linux内核自带usb鼠标驱动支持的usb_device_id结构体

static struct usb_device_id usb_mouse_id_table [] = {{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_MOUSE) },{ }  /* Terminating entry */
};MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);

当USB核心检测到某个设备的属性和某个驱动程序的usb_device_id结构体所携带的信息一致时,这个驱动程序的probe()函数就会被执行,拔掉设备或者卸载驱动模块后,USB核心就会执行disconnect()函数来响应这个动作。

上述usb_driver结构体中的函数是USB设备驱动中与USB相关的部分,而USB只是一个总线,USB设备驱动真正的主体工作仍然是USB设备本身所属类型的驱动,如字符设备,tty设备,块设备,输入设备等。因此USB设备驱动包含其作为总线上挂载设备的驱动和本身所属设备类型的驱动两部分。

与platform_driver,i2c_driver类似,usb_driver起到了“牵线”的作用,即在probe()里注册相应的字符,tty等设备,在disconnect()注销相应的设备,而原先对设备的注册和注销一般直接发生在模块加载和卸载函数中。

四:USB请求块介绍

USB请求块(URB)是USB设备驱动中用来描述与USB设备通信所用的基本载体和核心数据结构,USB设备中的每个端点都处理一个URB队列,在队列被清空之前,一个URB的典型生命周期如下:

1:被一个USB设备驱动创建,释放

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);void usb_free_urb(struct urb *urb);

iso_packets是这个URB应当包含的等时数据包的数目,若为0表示不创建等时数据包。mem_flags参数是分配内存的标志,和kmalloc()函数的分配标志参数含义相同。如果分配成功,该函数返回一个URB结构体指针,否则返回0.

2:初始化,被安排给一个特定USB设备的特定端点。对于中断URB,使用如下函数来初始化URB

static inline 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_fn,void *context,int interval);

(1)URB参数指向要被初始化的URB指针;

static struct urb *urb;

(2)dev指向这个URB要被发送到的USB设备;

struct usb_device *dev = interface_to_usbdev(intf);

(3)pipe是这个URB要被发送到的USB设备的特定端点;

int pipe;pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);

(4)transfer_buffer是指向发送数据或接收数据的缓冲区的指针,和URB一样,它也不能是静态缓冲器,必须使用kmalloc()来分配;

static char *transfer_buffer;transfer_buffer= usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys);

(5)buffer_length是transfer_buffer指针所指向缓冲区的大小;

static int len;
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
buffer_length = endpoint->wMaxPacketSize;

(6)complete_fn指针指向当这个URB完成时被调用的完成处理函数;

(7)context是完成处理函数的“上下文”;

(8)interval是这个URB应当被调度的间隔。

endpoint->bInterval

3:提交URB给USB核心

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);

提交URB到USB核心后,直到完成函数被调用之前,不要访问URB中的任何成员,usb_submit_urb()在原子上下文和进程上下文中都可以被调用,mem_flags变量需根据调用环境进行相应的设置:

  • GFP_ATOMIC:在中断处理函数,底半部,tasklet,定时器处理函数以及URB完成函数中,在调用者持有自旋锁或者读写锁时以及当驱动将current->state修改为非TASK_RUNNING时,应使用此标志。
  • GFP_NOIO:在存储设备块I/O和错误处理路径中,应使用此标志。
  • GFP_KERNEL:如果没有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用此标志。

如果usb_submit_urb()调用成功,即URB的控制权被移交给USB核心,该函数返回0;否则,返回错误号。

五:示例代码

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>static struct input_dev *usb_kbd_dev;         //input_dev
static unsigned char *usb_kbd_buf;            //虚拟地址缓存区
static dma_addr_t usb_kbd_phyc;               //DMA缓存区;
static int usb_kbd_len;                       //数据包长度
static struct urb *usb_kbd_urb;              //urbstatic const unsigned char usb_kbd_keycode[252] = {0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,150,158,159,128,136,177,178,176,142,152,173,140
};  //键盘码表共有252个数据static void myusb_kbd_irq(struct urb *urb)               //键盘中断函数
{static unsigned char buf1[8]={0,0,0,0,0,0,0,0};int i;/*上传左右crtl、shift、atl、windows按键*/for (i = 0; i < 8; i++){if(((usb_kbd_buf[0]>>i)&1)!=((buf1[0]>>i)&1)){    input_report_key(usb_kbd_dev, usb_kbd_keycode[i + 224], (usb_kbd_buf[0]>> i) & 1);input_sync(usb_kbd_dev);             //上传同步事件}     }/*上传普通按键*/for(i=2;i<8;i++){if(usb_kbd_buf[i]!=buf1[i]){if(usb_kbd_buf[i] )      //按下事件{input_report_key(usb_kbd_dev,usb_kbd_keycode[usb_kbd_buf[i]], 1); input_sync(usb_kbd_dev);             //上传同步事件                }else if(buf1[i])         //松开事件{input_report_key(usb_kbd_dev,usb_kbd_keycode[buf1[i]], 0);input_sync(usb_kbd_dev);             //上传同步事件                }}        }memcpy(buf1, usb_kbd_buf, 8);    //更新数据    usb_submit_urb(usb_kbd_urb, GFP_KERNEL);
}static int usb_kbd_probe(struct usb_interface *intf, const struct usb_device_id *id)
{volatile unsigned char  i;struct usb_device *dev = interface_to_usbdev(intf);                           struct usb_endpoint_descriptor *endpoint;                            struct usb_host_interface *interface;                                           int pipe;                                                                        interface=intf->cur_altsetting;                                                                   endpoint = &interface->endpoint[0].desc;                                        /* a. 分配一个input_dev */usb_kbd_dev=input_allocate_device();/* b. 设置 *//* b.1 能产生哪类事件 */set_bit(EV_KEY, usb_kbd_dev->evbit);set_bit(EV_REP, usb_kbd_dev->evbit);  /* b.2 能产生哪些事件 */for (i = 0; i < 252; i++)set_bit(usb_kbd_keycode[i], usb_kbd_dev->keybit);     //添加所有键clear_bit(0, usb_kbd_dev->keybit);/* c. 注册 */input_register_device(usb_kbd_dev);/* d. 硬件相关操作 *//* 数据传输3要素: 源,目的,长度 *//* 源: USB设备的某个端点 */pipe=usb_rcvintpipe(dev,endpoint->bEndpointAddress); /* 长度: */usb_kbd_len=endpoint->wMaxPacketSize;/* 目的: */usb_kbd_buf=usb_buffer_alloc(dev,usb_kbd_len,GFP_ATOMIC,&usb_kbd_phyc);/* 使用"3要素" *//* 分配usb request block */usb_kbd_urb=usb_alloc_urb(0,GFP_KERNEL);/* 使用"3要素设置urb" */   usb_fill_int_urb (usb_kbd_urb, dev, pipe, usb_kbd_buf, usb_kbd_len, myusb_kbd_irq, 0, endpoint->bInterval);             usb_kbd_urb->transfer_dma = usb_kbd_phyc;                  //设置DMA地址usb_kbd_urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;     //设置使用DMA地址/* 使用URB */usb_submit_urb(usb_kbd_urb, GFP_KERNEL);   return 0;
}static void usb_kbd_disconnect(struct usb_interface *intf)
{struct usb_device *dev = interface_to_usbdev(intf);      usb_kill_urb(usb_kbd_urb);usb_free_urb(usb_kbd_urb);usb_buffer_free(dev, usb_kbd_len, usb_kbd_buf,usb_kbd_phyc);input_unregister_device(usb_kbd_dev);                  //注销内核中的input_devinput_free_device(usb_kbd_dev);                        //释放input_dev
}static struct usb_device_id usb_kbd_id_table [] = {{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,USB_INTERFACE_PROTOCOL_KEYBOARD) },{ }                       /* Terminating entry */
};static struct usb_driver usb_kbd_drv = {.name        = "usbkbd_drv",.probe       = usb_kbd_probe,                        .disconnect  = usb_kbd_disconnect,.id_table    = usb_kbd_id_table,
};/*入口函数*/
static int usb_kbd_init(void)
{ usb_register(&usb_kbd_drv);return 0;
}/*出口函数*/
static void usb_kbd_exit(void)
{usb_deregister(&usb_kbd_drv);
}module_init(usb_kbd_init);
module_exit(usb_kbd_exit);
MODULE_LICENSE("GPL");

测试方法:

(1)配置内核,取消自带的usb键盘驱动,使用新内核启动

   -> Device Drivers -> HID Devices 

    <> USB Human Interface Device (full HID) support

(2)加载新驱动,插入usb键盘

(3)exec 0</dev/tty1  进入测试模式

exec 0<&-            退出测试模式

06_USB设备驱动相关推荐

  1. linux i2c核心,总线与设备驱动,Linux2.6.37 I2C驱动框架分析(一)

    最近工作中又使用到了I2C,所以借S3C2440开发板GT2440为硬件平台温习一遍I2C驱动体系. linux内核中IIC驱动的体系框架 linux内核中IIC部分驱动代码位于:/drivers/i ...

  2. ()shi linux字符设备,Linux字符设备驱动基础(三)

    Linux字符设备驱动基础(三) 6 创建设备节点 6.1 手动创建设备节点 查看申请的设备名及主设备号: cat /proc/devices # cat /proc/devices Characte ...

  3. linux驱动之i2c子系统mpu6050设备驱动

    以下是mpu6050简单的驱动实现,mpu6050是I2C接口的6轴传感器,可以作为字符设备注册到内核,本代码运行环境是3.4.2内核,4.3.2版本的编译链,12.04版本的Ubuntu,硬件环境是 ...

  4. linux设备驱动第五篇:驱动中的并发与竟态

    目录[-] 综述 信号量与互斥锁 Completions 机制 自旋锁 其他的一些选择 不加锁算法 原子变量与位操作 seqlock(顺序锁) 读取-拷贝-更新(RCU) 小结 综述 在上一篇介绍了l ...

  5. FreeBSD设备驱动管理介绍(BSP: Ti AM335x)

    这段时间一直在忙FreeBSD驱动移植的项目,因此对FreeBSD做了一定的了解,鉴于网上对于FreeBSD的设备驱动资料较少,在这里给出本人对于FreeBSD驱动管理的理解心得(主要是USB驱动管理 ...

  6. linux下i2c设备驱动程序,Linux I2C 设备驱动

    I2C 设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数.i2c_client 一般被包含在设备的私有信息结构体yyy_data 中,而 i2c_drive ...

  7. windows 7 下安装 Android 设备驱动

    为什么80%的码农都做不了架构师?>>>    window xp 我不知道,很久不用xp了. 1.将设备通过USB线连接PC机,这个时候系统会自动安装驱动,但是最后提示不成功: 2 ...

  8. 乾坤合一~Linux设备驱动之终端设备驱动

    多想拥你在我的怀里 却无法超越那距离 美好回忆渐渐地远去 盼望今生出现奇迹 无尽的想念 荒了容颜 无助的爱恋 从未改变 这是今天的旋律,,,,今生今世,遥不可及~ 1 终端设备 终端是一种字符型设备, ...

  9. Linux驱动无硬件设备,Linux设备驱动与硬件通信

    Linux物理设备驱动,主要有几种类型,如:IO类.内存类.总线类.IO类我们平时接触的最多,其主要特点是,通过IO设备的寄存器操作硬件,具体需要去查看硬件手册. 1. IO端口和IO内存 在硬件层, ...

最新文章

  1. 10年Java老兵宝藏资料,吐血奉献!
  2. selenium 操作 html 5,[Selenium] WebDriver 操作 HTML5 中的 video
  3. 防范数据中心火灾的7个方式
  4. web worker原理 SSE原理
  5. scratch的积木相与java的_scratch课堂:积木块详解
  6. 背景图片hover加蒙层_css3实现图片遮罩效果鼠标hover以后出现文字
  7. ABBYY PDF Transformer+功能概述
  8. vue报错 | Duplicate keys detected: ‘0’. This may cause an update error.
  9. C语言实现 神奇魔方
  10. php 计算工资,php计算税后工资的方法
  11. Windows系统压缩卷时可压缩空间远小于实际剩余空间解决方法
  12. 华硕bios更改固态硬盘启动_华硕bios如何设置固态硬盘启动?
  13. 轮播图特效 html+css+js
  14. android 可以iphone,[实测]哪些Android手机充电器也可以给iPhone 12快充?
  15. 百度地图多标注显示以及自定义图标
  16. 谁说前端不能搞红黑树,用这55张图拿JS一起手撕红黑树
  17. 利用flex弹性布局实现图片水平及垂直方向居中
  18. Win11宽带连接错误651如何解决?
  19. react(let命令)
  20. 群晖服务器+微信同步,群晖服务器 云同步

热门文章

  1. C语言openssl aes-128-ecb加解密
  2. cocos creator 绘制闪电特效
  3. Spring学习笔记(一):眼见为实,先上一个简单例子
  4. 开发webgl应用时,cesium快速定位相机角度、相机位置的方法
  5. android 手机存储大小设置,安卓手机怎么设置增加虚拟内存
  6. Android Studio 制作微信界面 上
  7. 2017美国计算机专业排名,2017年美国大学计算机专业排名TOP121
  8. Vue 的身份证 手机号码 电话号码 邮箱等校验
  9. 了解并掌握Halcon HDevelop 仿真程序语法
  10. wechat小程序布局flex