linux USB开发笔记

1.usb驱动程序的编写:分析linux内核的usb骨架代码usb_skeleton.c
说明:一个usb设备有若干个配置组成,每一个配置又可以有多个接口,每一个接口又有多个设置
而接口本身又有可能没有端点或者多个端点。usb的数据交换是通过端点进行的。
usb的接口分为控制接口、中断接口、批量接口、等时接口。

在linux中端点使用数据接口struct usb_host_endpoint来描述usb的端点信息
在linux中接口使用struct usb_interface来描述。
在linux中整个usb设备可以使用struct usb_device结构来描述。
在linux的usb驱动程序中,ehci每次发送的数据包大小最好为512,根据linux内核代码说明得出的
/* MAX_TRANSFER is chosen so that the VM is not stressed by
   allocations > PAGE_SIZE and the number of packets in a page
   is an integer 512 is the largest possible packet on EHCI */

usb驱动程序编写流程:
1.注册usb子系统usb_register(&skel_driver);
说明:skel_driver是描述驱动信息的结构体




定义一个id_table结构体,来描述内核允许该模块所支持的硬件设备
#define USB_SKEL_VENDOR_ID 0xfff0
#define USB_SKEL_PRODUCT_ID 0xfff0
/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
这个结构体的最后一个元素必须为空,用来标识结束符


MODULE_DEVICE_TABLE(usb, skel_table);
module device table用来描述设备类型,如果是usb设备,自然是usb,如果是pci设备那么就是MODULE_DEVICE_TABLE(pci, skel_table);




static struct usb_driver skel_driver = {
.name = "skeleton", /* 驱动名称 */
.probe = skel_probe, /* 操作函数  */
.disconnect = skel_disconnect, /* 断开函数 */
.suspend = skel_suspend,
.resume = skel_resume,
.pre_reset = skel_pre_reset,
.post_reset = skel_post_reset,
.id_table = skel_table,
.supports_autosuspend = 1,
};
其中name probe disconnect 以及id_table是必须的,其他的可以看情况添不添加。
id_table用来告诉内核该模块支持的设备,usb子系统通过设备的protect id和vendor id_table
的组合或者设备的class、subclass和protocol的组合来识别设备。


如果一个usb设备接入到系统中,他的protect id和vendor id符合我们注册的,那么久会调用这个模块
作为该设备的驱动程序。






2.定义一个描述该驱动以及拥有所有资源和状态的结构体,这个结构体是程序员自己按照驱动需求定义的
struct usb_skel {
struct usb_device*udev; /* the usb device for this device */
struct usb_interface*interface; /* the interface for this device */
struct semaphorelimit_sem; /* limiting the number of writes in progress */
struct usb_anchorsubmitted; /* in case we need to retract our submissions */
unsigned char           *bulk_in_buffer;/* the buffer to receive data */
size_t bulk_in_size; /* the size of the receive buffer */
__u8 bulk_in_endpointAddr;/* the address of the bulk in endpoint */
__u8 bulk_out_endpointAddr;/* the address of the bulk out endpoint */
int errors;/* the last request tanked */
int open_count;/* count the number of openers */
spinlock_t err_lock; /* lock for errors */
struct kref kref;
struct mutex io_mutex; /* synchronize I/O with disconnect */
};
分析:这个结构体拥有一个描述usb设备的结构体udev,一个接口interface,一个用于接收数据的输入缓冲bulk_in_buffer
以及大小bulk_in_size,批量的输入输出端口地址bulk_in_endpointAddr、bulk_out_endpointAddr。这些变量是一个
usb驱动程序所必须使用到的






3.编写probel函数static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id)
当调用这个函数的时候,系统会传递一个usb_interface和id 他们分别是该usb设备的接口描述符(一般会是该设备的第0号接口
,该接口的0号配置,以及设备描述id--protect id和vendor id)
skel_probe函数主要需要实现下面的一些代码程序:
dev->udev = usb_get_dev(interface_to_usbdev(interface));
说明:interface_to_usbdev(interface) /* 得到该usb的设备描述符 */
usb_get_dev函数是增加对该usb_device的引用计数的,我们应该在操作该设备的时候增加引用计数,释放usb的时候减少引用计数
注意:这里的引用计数是对usb_device的计数,不是对模块的计数,在编写驱动程序的时候我们可以不进行计数。


dev->interface = interface; /* 得到该设备的接口描述符 */


在得到usb_device之后,我们需要对usb_skel结构体进行初始化,这部分工作的任务主要是向usb_skel注册该usb设备的端点
/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
iface_desc = interface->cur_altsetting;/* 获取当前接口设置 */
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;/* 得到端点描述符 */


if (!dev->bulk_in_endpointAddr &&
   usb_endpoint_is_bulk_in(endpoint)) {/* 检查端点的类型和输出方向 */
/* we found a bulk in endpoint */
/* 如果我们发现他是输入的端点,那么就注册到usb_skel中 */
buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);/* 分配输出缓存 */
if (!dev->bulk_in_buffer) {
err("Could not allocate bulk_in_buffer");
goto error;
}
}


/* 如果检查到该usb的端点类型是输出端点 保存输出端点的信息到usb_skel中*/
if (!dev->bulk_out_endpointAddr &&
   usb_endpoint_is_bulk_out(endpoint)) {
/* we found a bulk out endpoint */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
err("Could not find both bulk-in and bulk-out endpoints");
goto error;
}
}
说明:在usb_host_interface结构体里面有一个usb_interface_descriptor的叫做desc的成员,它用来描述interface
的一些属性,bNumEndpoints是一个8位的数字,代表端口的端点数,proble函数便利所有的端点,检查他们的类型和方向
,并且注册到usb_skel中 端点是根据HID规范来的(什么是HID规范)


usb_set_intfdata(interface, dev); /* 向系统注册 usb_skel*/
说明:这个函数注册的数据结构是任意的,只是为了我们以后用usb_get_intfdata可以得到我们想要的数据结构




接下来我们可以注册一个文件操作函数集,这个才是usb设备的真正操作函数集合
static struct usb_class_driver skel_class = {
.name = "skel%d",
.fops = &skel_fops,
.minor_base = USB_SKEL_MINOR_BASE,
};


/* we can register the device now, as it is ready */
retval = usb_register_dev(interface, &skel_class);
if (retval) {
/* something prevented us from registering this driver */
err("Not able to get a minor for this device.");
usb_set_intfdata(interface, NULL);
goto error;
}






4.usb设备断开函数
当设备被拔出集线器的时候,usb子系统会自动的调用disconnect函数,它主要是注销class_device
static void skel_disconnect(struct usb_interface *interface)
{
struct usb_skel *dev;
int minor = interface->minor;


dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);


/* give back our minor */
usb_deregister_dev(interface, &skel_class);


/* prevent more I/O from starting */
mutex_lock(&dev->io_mutex);
dev->interface = NULL;
mutex_unlock(&dev->io_mutex);


usb_kill_anchored_urbs(&dev->submitted);


/* decrement our usage count */
kref_put(&dev->kref, skel_delete);


dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor);
}




usb操作函数集接口初始化流程说明:
static const struct file_operations skel_fops = {
.owner = THIS_MODULE,
.read = skel_read,
.write = skel_write,
.open = skel_open,
.release = skel_release,
.flush = skel_flush,
};
在这个结构体里面的操作函数集主要是对usb设备的具体的操作,包括数据的发送与接收,usb数据的发送与接收是通过urb进行的
urb好比高速公路上的汽车,他可以运载usb的4种数据流,不过你需要向告诉司机李需要运什么,目的地是什么。


1.创建urb usb_alloc_urb(0, GFP_KERNEL);
第一个函数是等时包的数量,如果不是承载的等时包,应该为0,第二个参数与kmalloc。
要释放一个urb可以用:usb_free_urb函数
要承载数据,我们还需要告诉司机目的地址信息和需要运送的货物,对于不同的数据,系统提供了不同的函数(4种)
下面是一个urb使用的方法(批量传输)
usb_fill_bulk_urb(urb, dev->udev,
 usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
 buf, writesize, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* 不要再让HCD做DMA映射,相当于DMA映射已经做好了 */
usb_anchor_urb(urb, &dev->submitted);


/* send the data out the bulk port */
retval = usb_submit_urb(urb, GFP_KERNEL);


说明:skel_write_bulk_callback是回调函数我们可以在urb传输完成之后通过status判断urb是否正常传输成功
如果不成功,正确的做法应该是重新提交urb。
static void skel_write_bulk_callback(struct urb *urb)
{
struct usb_skel *dev;
dev = urb->context;
/* sync/async unlink faults aren't errors */

/* 正确的做法是如果urb传输错误应该重新提交urb */
if (urb->status) {
printk("urb error!\r\n");/* 如果urb提交错误 */
}
else
printk("urb success!\r\n");/* 如果正常提交urb */


}
-


usb_sndbulkpipe:这个函数是根据设备和端点生成管道,这里的管道是批量发送管道,对应的还有
#define usb_sndctrlpipe(dev,endpoint) 控制发送管道
#define usb_rcvctrlpipe(dev,endpoint) 控制接收管道
#define usb_sndisocpipe(dev,endpoint) 等时发送管道
#define usb_rcvisocpipe(dev,endpoint) 等时接收管道
#define usb_sndbulkpipe(dev,endpoint) 批量发送管道
#define usb_rcvbulkpipe(dev,endpoint) 批量接收管道
#define usb_sndintpipe(dev,endpoint) 中断发送管道
#define usb_rcvintpipe(dev,endpoint) 中断接收管道




说明:一个参数是urb,第二个参数是usb的设备,第三个参数是管道号码pipe。该管道记录了目标设备的端点以及管道的类型
,每一个管道只有一种类型和一个方向,它与他的目标设备的端点相对应,usb_sndbulkpipe函数可以把指定的usb设备的指定端点
这里指定成为一个批量out端点(还有类似的函数可以设置成为控制out端点,控制in端点,批量in端点,批量out端点,中断in端点,
中断out端点,等时int端点,等时out端点,等时in端点),第四个参数是需要发送的数组,第五个参数是发送数组的长度,
第六个参数是发送完成之后的回调函数,当发送完成之后,这个函数会被自动调用,一般在这个函数中,
可以通过判断urb->status的值,来判断成功传输与否。第六个参数是用户自己定义的,是可能在回调函数中使用的数据。


注意:在write中申请的部分资源,比如说每次调用write都会申请的资源,例如urb和buff,强烈建议在发送完成的回调函数中手动
释放。


urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* 这句话的意思就是使用dma传输 */
usb_submit_urb(urb, GFP_KERNEL); /* 启动usb传输 */








事实上,如果数据量不大,那么可以不一定需要用卡车来运货,系统还提供了一种不用urb的传输方式,而usb_skeleton的读操作正式
采用了这种方式实现。
/* do a blocking bulk read to get data from the device */
retval = usb_bulk_msg(dev->udev,
 usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
 dev->bulk_in_buffer,
 min(dev->bulk_in_size, count),
 &bytes_read, 10000);


/* if the read was successful, copy the data to userspace */
if (!retval) {
if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
retval = -EFAULT;
else
retval = bytes_read;
}
函数原型:int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout)
说明:这个函数会阻塞等待数据的传输完成或者超时,data是输入/输出缓冲,len是它的长度大小,actual_length
是实际传输的数据的大小,timeout设置超时,这个函数也是批量传输数据的usb发送函数,只不过没有使用urb。






以上是发送urb,如果是需要接收数据,那么只需要调用接收urb函数即可
retval = usb_bulk_msg(dev->udev,
 usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
 dev->bulk_in_buffer,
 min(dev->bulk_in_size, count),
 &bytes_read, 10000);
以上就是usb发送和接收的全部流程。
















块设备驱动程序:
块设备读写数据的基本单位是块,例如一个磁盘通常为一个sector(扇区)
block_device结构代表了内核中的一个块设备,他可以标识整个磁盘或者一个特定的分区。当这个结构代表一个
分区的时候,它的bd_contains成员指向包含这个分区的设备,bd_part成员指向设备的gendisk结构。


gendisk是一个单独的磁盘驱动器的内核表示,内核还使用gendisk来表示分区。
gendisk结构的操作函数集合包含了一下几个:alloc_gendisk /* 分配磁盘 */ add_disk /* 增加磁盘信息 */
unlink_gendisk /* 删除磁盘信息 */ delete_partition /* 删除分区 */ add_partition /* 添加分区 */


block_device_operations类似于支付设备驱动里面的file_operations结构,它是块设备的对应接口,
这里面定义的是块设备的操作函数集
函数原型:
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t,
void **, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
说明:


static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};


系统对块设备进行读取操作时,是通过块设备通用的读写操作函数将一个请求保存在该设备的操作请求队列中,
然后调用这个块设备的底层处理函数,对请求队列中的操作请求进行逐一执行,request_queue结构描述了块设备
的请求队列。
请求队列拥有一系列相关的处理函数: 创建队列时提供了一个自旋锁blk_init_queue、获取队列中第一个未完成的请求
elv_net_request、请求完成end_request、请求停止blk_stop_queue、开始请求blk_start_queue、清除请求队列blk_clenup_queue


块设备驱动程序编程:
1.初始化请求队列
blk_init_queue(do_ramblock_request, &ramblock_lock);
说明:该函数的第一个参数是请求处理函数的指针,第二个参数是控制访问队列的权限的自旋锁(在编写对应的驱动程序的时候,一定要检测返回值)。
请求处理函数不能由驱动自己调用,只有当内核认为是时候让驱动处理对设备的读写等操作时,它才会调用这个函数。
对于ramdisk这种完全随机访问的非机械设备,并不需要复杂的I/O调度,这个时候,可以直接踢开"I/O调度器",使用如下的
函数来绑定请求队列和制造请求的函数(make_request_fn)
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
说明:blk_alloc_queue和blk_queue_make_request结合起来使用的逻辑一般是
xxx_queue = blk_alloc_queue(GPF_KERNEL)
blk_queue_make_request(xxx_queue, xxx_make_request)


2.注册块设备
register_blkdev(0, "ramblock");  /* cat /proc/devices */
说明:块设备的注册和支付设备一样的,有自己的设备名字和设备号,如果主设备号为0,那么内核会自动分配一个新的主设备号,
一般情况下,默认写0。该函数正常返回的是主设备号,就是major的值


3.分配一个gendisk结构(gendisk在linux内核中用来标识一个独立的设备或者分区)
alloc_disk(16); /* 次设备号个数: 分区个数+1 */
说明:alloc_disk的参数 代表次设备号,同一个磁盘的各个分区共享一个主设备号,而此设备号则不相同,所以这里的此设备号
可以用来代表分区的个数,因为次设备号是从0开始的,所以是参数+1个分区。




4.当我们分配好gendisk结构后,我们需要初始化gendisk,设置其属性
1).设置gendisk的请求队列ramblock_disk->queue = ramblock_queue;
2).设置主设备号ramblock_disk->major       = major;
3).分配次设备号ramblock_disk->first_minor = 0; /* 相当于分区的起始号 在此后的程序中该值不能够修改*/
4).指定块设备的操作函数集合ramblock_disk->fops        = &ramblock_fops;
5).设置gendisk的name字段sprintf(ramblock_disk->disk_name, "ramblock");
6).设置扇区大小set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); /* 配置各个扇区的容量 */
7).注册磁盘设备(gendisk结构分配之后还不能马上被使用)add_disk(ramblock_disk);


说明:在块设备的卸载过程中完成与模块加载函数相反的工作
1)清除请求队列,使用blk_cleanup_queue
2)删除对gendisk的引用,使用put_disk
3)删除对块设备的引用,注销块设备驱动,使用unregister_blkdev


5.在我们初始化gendisk结构体之后,我们需要填充file_ops结构
主要的操作函数说明:
1)获取驱动器信息int (*getgeo)(struct block_device *, struct hd_geometry *);(疑问:这个函数在什么时候被调用?)
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 这些值得设置是按照我们分配的内存大小设置出来的 */
/* 容量=heads*cylinders*sectors*512 */
geo->heads     = 2;
geo->cylinders = 32;
geo->sectors   = RAMBLOCK_SIZE/2/32/512;
return 0;
}




6.编写request_queue请求函数
1)提交请求blk_peek_request(struct request_queue *q)
说明:上述函数返回一个要处理的请求(由I/O调度器决定),如果没有请求则返回NULL,它不会清楚请求,而是仍然将这个请求保留在队列
上,原来的老函数elv_next_request函数已经不再使用了(新版本的内核才会使用提交请求blk_peek_request,如果内核中没有这个函数,请
使用老版本的函数elv_next_request)。


2)启动请求
void blk_start_request(struct request *req)
说明:从请求队列中移除请求,原先老的API blkdev_dequeue_request()会在blk_start_request()内部被调用。
我们可以考虑是用blk_fetch_request()函数,它同时做完了blk_peek_request和blk_start_request的工作。


3)报告完成
它拥有一系列的blk_end_xxx函数,如果我们使用了blk_queue_make_request绕开了io调度,那么在bio出来完成之后使用bio_endio()
函数来通知处理结束。












i2c驱动编写: 参考韦东山的i2c驱动代码和linux内核自带的驱动代码
i2c驱动主要分为linux i2c总线驱动和设备驱动,总线驱动一般已经提供好了的,我们主要是开发对应的具体外设的设备驱动
说明:编写i2c设备驱动有两种方法,一种是利用系统给我们提供的i2c-dev.c来实现一个i2c适配器来控制i2c设备,我们需要在应用
程序去封装数据,我们需要做的就是在应用层来填充i2c_msg结构体来向驱动层传递数据。另一种是为了i2c
设备,独立编写一个设备驱动程序。说明:在后面一种情况下,不需要使用i2c-dev.c。


利用i2c-dev.c作为适配器,进而控制i2c设备。i2c-dev.c并没有针对特定的设备而实现,只是提供了通用的read、write和ioctl等接口
应用层可以借用这些接口访问挂载在i2c适配器上面的i2c设备。
说明:该文件只适合单开始信号,对于多开始信号需要单独编写i2c设备驱动程序




下面分析i2c的普通驱动,不使用i2c-dev.c




注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!
第一个设备驱动的写法:在板级中填充关于i2c设备的硬件信息,i2c_client是内核根据板级代码自动生成的
不需要我们单独来填充i2c_client结构。(i2c-adapter)




1.填充板级信息,使用i2c_board_info结构体
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned shortflags; /* 标志位 */
unsigned shortaddr; /* 设备地址 */
void *platform_data;/* 用来传递私有的数据 */
struct dev_archdata*archdata; /* 也可以用来传递私有的数据 */
int irq;/* 中断号 */
};
说明:i2c_board_info必须初始化两个字段,分别是设备名和设备地址,对应了i2c_client的name以及i2c_client的addr。


说明:type是设备名称,它和驱动程序的name字段进行匹配,使用方式类似于下面的(从linux内核摘录)
static struct twl4030_platform_data sdp3430_twldata = {
.irq_base = TWL4030_IRQ_BASE,
.irq_end = TWL4030_IRQ_END,


/* platform_data for children goes here */
.bci = &sdp3430_bci_data,
.gpio = &sdp3430_gpio_data,
.madc = &sdp3430_madc_data,
.keypad = &sdp3430_kp_data,
.usb = &sdp3430_usb_data,


.vaux1 = &sdp3430_vaux1,
.vaux2 = &sdp3430_vaux2,
.vaux3 = &sdp3430_vaux3,
.vaux4 = &sdp3430_vaux4,
.vmmc1 = &sdp3430_vmmc1,
.vmmc2 = &sdp3430_vmmc2,
.vsim = &sdp3430_vsim,
.vdac = &sdp3430_vdac,
.vpll2 = &sdp3430_vpll2,
};
static struct i2c_board_info __initdata sdp3430_i2c_boardinfo[] = {
{
I2C_BOARD_INFO("twl4030", 0x48),/* 提示:i2c设备本来的地址是8位,但是linux和裸机程序是有一定的区别的,这里不能够使用8
位来代表设备地址,一般用高7位来代表i2c的设备地址,一般真实的地址右移一位得到linux的i2c的设备地址 */
.irq = INT_34XX_SYS_NIRQ,
.platform_data = &sdp3430_twldata,
},
};


2.注册平台设备
板子上没有一个i2c设备,那么我们就需要注册一个i2c平台设备,注册函数如下:
i2c_register_board_info(1, sdp3430_i2c_boardinfo, ARRAY_SIZE(sdp3430_i2c_boardinfo));
说明:第一个参数是bus号,第二个参数是board_info结构,第三个参数是大小。




如果按照以上的这种方式填充,那么我们不需要填充i2c_client结构,这个结构会被操作系统根据i2c_board_info结构
自动填充。




注意:i2c的设备i2c_client不是在i2c_register_board_info的时候填充的,是在创建i2c_adapter的时候填充的,它会根据我们写的
板级信息来填充。同时也会初始化adapter结构,一个i2c的设备想要进行工作,有三个过程是必不可少的,创建i2c_cilent,创建i2c_adapter
创建i2c_driver。
struct i2c_client {
unsigned short flags;/* div., see below */
unsigned short addr;/* chip address - NOTE: 7bit */
/* addresses are stored in the*/
/* _LOWER_ 7 bits*/
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;/* the adapter we sit on */
struct i2c_driver *driver;/* and our access routines */
struct device dev;/* the device structure */
int irq; /* irq issued by device */
struct list_head list;/* DEPRECATED */
struct list_head detected;
struct completion released;
};


3.注册i2c_driver和匹配对应的i2c_client(一个i2c_client对应一个相应的i2c设备,i2c_client携带的是设备相关的硬件信息)


每一个i2c的设备驱动,必须首先创建一个i2c_driver结构体,该结构体包含了i2c设备探测和注销的一些基本的方法和信息
我们在编写对应的设备驱动的时候主要填充以下字段
static const struct i2c_device_id ds1682_id[] = {
{ "at24cxx", 0 },
{ }
};
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",/* 驱动名称 */
},
.probe = at24cxx_probel,/* 匹配函数 */
.command = NULL,
};
编写好结构体信息之后,需要在模块加载函数中注册
i2c_add_driver(&at24cxx_driver);
使用i2c_add_driver向内核注册i2c_驱动。一旦驱动和设备文件相匹配,那么probel函数将会被调用。


说明:一般情况下只需要定义probel函数以及remove函数即可,




4.在proble函数中我们需要定义操作函数集(字符驱动函数集)
static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read  = at24cxx_read,
.write = at24cxx_write,
};




struct i2c_client *at24cxx_client; /* 需要提前定义好,在驱动程序中 */
static int at24cxx_probel(struct i2c_client *client,
const struct i2c_device_id *id)
{
int rc;
at24cxx_client = client;/* 说明,这里的client是内核根据我们填充的板级信息自动生成的 */
major = register_chrdev(0, "at24cxx", &at24cxx_fops);


cls = class_create(THIS_MODULE, "at24cxx");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
return rc;
}




5.定义i2c的write和read函数


static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret;
if (size != 1)
return -EINVAL;

copy_from_user(&address, buf, 1);


/* 数据传输三要素: 源,目的,长度 */


/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr  = at24cxx_client->addr;  /* 目的 */
msg[0].buf   = &address;              /* 源 */  
msg[0].len   = buf->len;                     /* 地址=1 byte */
msg[0].flags = buf->flag;                     /* 表示写 */


/* 然后启动读操作 */
msg[1].addr  = at24cxx_client->addr;  /* 源 */
msg[1].buf   = &data;                 /* 目的 */
msg[1].len   = 1;                     /* 数据=1 byte */
msg[1].flags = I2C_M_RD;                     /* 表示读 */




/* 启动传输 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (ret == 2)
{
copy_to_user(buf, &data, 1);
return 1;
}
else
return -EIO;
}




static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char val[2];
struct i2c_msg msg[1];
int ret;

/* address = buf[0] 
* data    = buf[1]
*/
if (size != 2)
return -EINVAL;

copy_from_user(val, buf, 2);


/* 数据传输三要素: 源,目的,长度 */
msg[0].addr  = at24cxx_client->addr;  /* 目的 */
msg[0].buf   = val;                   /* 源 */
msg[0].len   = 2;                     /* 地址+数据=2 byte */
msg[0].flags = 0;                     /* 表示写 */


ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (ret == 1)
return 2;
else
return -EIO;
}






注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!
第二个设备驱动的写法:我们在i2c驱动程序手动填写关于i2c设备相关的硬件信息(设备地址,访问的时钟频率等)


1.定义一个struct i2c_client *at24cxx_client;结构体,用来描述硬件信息。(在程序中进行填充)
定义一个i2c_driver结构体,因为这里不存在平台设备和驱动匹配的情况,所以不需要probel函数。
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach,/*  */
.detach_client  = at24cxx_detach,/*  *
}
定义一个i2c_client_address_data结构体,这个结构体用用来描述设备地址
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces     = forces, /* 强制认为存在这个设备 */
};




3.设备的注册以及探测功能
这一步很关键,按照标准的要求来写,则linux系统会自动调用相关的代码去探测你的i2c设备,并且添加到系统的i2c设备列表中以供后面访问
i2设备的探测一般是靠设备地址来完成的,那么,首先就需要在驱动代码中声明里要探测的i2c设备的地址列表,以及一个宏。
在这里我们自己定义一个数组来代表i2c设备的地址(提示:i2c设备本来的地址是8位,但是linux和裸机程序是有一定的区别的,这里不能够使用8
位来代表设备地址,一般用高7位来代表i2c的设备地址,一般真实的地址右移一位得到linux的i2c的设备地址)s
tatic unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
定义的i2c设备的地址数组必须要以I2C_CLIENT_END字段进行结尾。






#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>


static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
                                        /* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */


static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces     = forces, /* 强制认为存在这个设备 */
};


static struct i2c_driver at24cxx_driver;




static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;


static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret;

/* address = buf[0] 
* data    = buf[1]
*/
if (size != 1)
return -EINVAL;

copy_from_user(&address, buf, 1);


/* 数据传输三要素: 源,目的,长度 */


/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr  = at24cxx_client->addr;  /* 目的 */
msg[0].buf   = &address;              /* 源 */
msg[0].len   = 1;                     /* 地址=1 byte */
msg[0].flags = 0;                     /* 表示写 */


/* 然后启动读操作 */
msg[1].addr  = at24cxx_client->addr;  /* 源 */
msg[1].buf   = &data;                 /* 目的 */
msg[1].len   = 1;                     /* 数据=1 byte */
msg[1].flags = I2C_M_RD;                     /* 表示读 */




ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (ret == 2)
{
copy_to_user(buf, &data, 1);
return 1;
}
else
return -EIO;
}


static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char val[2];
struct i2c_msg msg[1];
int ret;

/* address = buf[0] 
* data    = buf[1]
*/
if (size != 2)
return -EINVAL;

copy_from_user(val, buf, 2);


/* 数据传输三要素: 源,目的,长度 */
msg[0].addr  = at24cxx_client->addr;  /* 目的 */
msg[0].buf   = val;                   /* 源 */
msg[0].len   = 2;                     /* 地址+数据=2 byte */
msg[0].flags = 0;                     /* 表示写 */


ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (ret == 1)
return 2;
else
return -EIO;
}




static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read  = at24cxx_read,
.write = at24cxx_write,
};


static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
printk("at24cxx_detect\n");


/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr    = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver  = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx");

/* 将i2c_client于i2c_adapter关联起来 */
i2c_attach_client(at24cxx_client);

major = register_chrdev(0, "at24cxx", &at24cxx_fops);


cls = class_create(THIS_MODULE, "at24cxx");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */

return 0;
}


static int at24cxx_attach(struct i2c_adapter *adapter)
{
/* 初始化适配器,初始化回调函数 */
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}


static int at24cxx_detach(struct i2c_client *client)
{
printk("at24cxx_detach\n");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "at24cxx");


i2c_detach_client(client);
kfree(i2c_get_clientdata(client));


return 0;
}




/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = "at24cxx",
},
.attach_adapter = at24cxx_attach,/* 这个函数是相当于probel的作用 */
.detach_client  = at24cxx_detach,
};


static int at24cxx_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}


static void at24cxx_exit(void)
{
i2c_del_driver(&at24cxx_driver);
}


module_init(at24cxx_init);
module_exit(at24cxx_exit);


MODULE_LICENSE("GPL");





























































  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值