usb驱动在windows系统下只用支持主流的WinXp和Win7,代码就一套,编译出32位和64位两个版本release给用户就ok了。
但在linux系统下就不一样了,众多的linux内核版本,即使常用的2.6.y和3.x.y都有好多种,针对每个版本内核都编译一把显然不现实。我们的做法是直接把驱动源码和Makefile发给客户,让客户自己编译。
但直接源码发给用户显然不符合公司的利益,上周老大让我试试看能不能把源码中核心内容编译成.a的静态库,加上一个.c的空壳release给用户。
当前驱动编译目录中只有三个文件:usb_drv.c(源码文件),usb_drv.h(接口文件,一些自定义的结构体和命令宏),Makefile。
usb_drv.c:
#include <linux/kernel.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kref.h> #include <asm/uaccess.h> #include <linux/usb.h> #include <linux/version.h> /*compare kernel version*/ ///<<<<<Added compiler support for RHEL6 (2.6.32-71.el6.i686 SMP) //#include <linux/smp_lock.h> //<<<<<Added compiler support for Ubuntu12.10 (3.5.0-17-generic SMP) #include <linux/mutex.h> DEFINE_MUTEX(os_mutex); // define a mutex //<<<<<< #include "usb_drv.h" ...... static int Drv_Open(struct inode *inode, struct file *file) { ...... } static int Drv_Close(struct inode *inode, struct file *file) { ...... } static ssize_t Drv_Read(struct file *file, char *buffer, size_t count, loff_t *ppos) { ...... } static ssize_t Drv_Write(struct file *file, const char *user_buffer, size_t count, loff_t *ppos) { ...... } #if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0) int Drv_IOCtl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) #else //<<<<<Added compiler support for Linux kenerl v3.0 int Drv_IOCtl(struct file *file, unsigned int cmd, unsigned long arg) #endif { ...... } static struct file_operations xxx_fops = { .owner = THIS_MODULE, .read = Drv_Read, .write = Drv_Write, #if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0) .ioctl = Drv_IOCtl, #else .unlocked_ioctl = Drv_IOCtl, .compat_ioctl = Drv_IOCtl, #endif .open = Drv_Open, .release = Drv_Close, }; static struct usb_class_driver xxx_class = { .name = "tailm", .fops = &xxx_fops<span style="font-family: Arial, Helvetica, sans-serif;">,</span> .minor_base = XXX_MINOR_BASE, }; static int Drv_Probe(struct usb_interface *interface, const struct usb_device_id *id) { ...... } static void Drv_Disconnect(struct usb_interface *interface) { ...... } static struct usb_device_id xxx_table [] = { { USB_DEVICE(XXX_VENDOR_ID, XXX_PRODUCT_ID) }, { } }; MODULE_DEVICE_TABLE (usb, xxx_table); static struct usb_driver xxx_driver = { .name = "xxx", .probe = Drv_Probe, .disconnect = Drv_Disconnect, .id_table = xxx_table, }; static int __init Drv_Init(void) { int result; printk("Drv_Init!"); result = usb_register(&xxx_driver); if (result) printk("usb_register failed. Error number %d", result); return result; } static void __exit Drv_Release(void) { printk("Drv_Release!"); usb_deregister(&xxx_driver); } module_init (Drv_Init); module_exit (Drv_Release); MODULE_LICENSE("GPL");
usb_drv.h:
#ifndef _XXX_DRV_H #define _XXX_DRV_H_ #define XXX_IOC_MAGIC 'x' #define IOCTL_Ezusb_VENDOR_REQUEST _IOWR(XXX_IOC_MAGIC, 0x03, unsigned char[4]) #define IOCTL_Ezusb_GET_DESCRITION _IOWR(XXX_IOC_MAGIC, 0x04, unsigned char[4]) // for read usb description typedef struct _VENDOR_REQUEST_IN { unsigned char bRequest; unsigned short wValue; unsigned short wIndex; unsigned short wLength; unsigned char direction; unsigned char bData; } VENDOR_REQUEST_IN, *PVENDOR_REQUEST_IN; typedef struct _DEVICE_DESCRPTION_IN { unsigned char u8Len; char *pcDescription; } DEVICE_DESCRPTION_IN, *PDEVICE_DESCRPTION_IN; #endif /*_TAILM_DRV_H_*/
Makefile:
CPPS = usb_drv.c SRCFOLDER = $(shell pwd) OBJS = $(patsubst %.cpp,%.o, $(CPPS)) TARGET_OBJS = $(foreach obj, $(OBJS), $(SRCFOLDER)/$(obj)) SOURCE = $(foreach cpp, $(CPPS), $(SRCFOLDER)/$(cpp)) TARGET = drv obj-m = $(TARGET).o drv-objs := usb_drv.o CURPATH = $(shell pwd) KERNEL_DIR = /lib/modules/$(shell uname -r)/build CFLAGS += -mcmodel=kernel PWD = $(shell pwd) .PHONY = all load unload reload clean all : $(TARGET) $(TARGET): $(MAKE) -C $(KERNEL_DIR) CFLAGS="$(CFLAGS)" M=$(PWD) $(TARGET_OBJS) : $(SOURCE) $(CC) -o $@ $(CFLAGS) -c $< load: insmod $(TARGET).ko unload: rmmod $(TARGET).ko reload: unload load clean: @rm -rf *.ko *.o *.mod.c Module.symvers *.unsigned *.order *.cmd .*
Note:1.编译驱动前要安装相应版本的内核开发包。
开始动手修改代码,我开始是想着把usb_drv.c中除了Drv_Init()和Drv_Release()留下之外其他的api都移到新建文件usb_drv_lib.c中去(usb_drv_lib.c用来编译静态库usb_drv_lib.a)。
再说说静态库usb_drv_lib.a,由于调用了内核api,它属于内核静态库,编译有别于普通静态库。具体可参考:http://blog.csdn.net/boywhp/article/details/6063496
usb_drv_lib.a的Makefile_lib写法:
RM = rm -f CCFLAGS = -c -O2 -D__KERNEL__ ARFLAG = -rc CC = gcc -fno-common -v AR = ar lib_OBJECTS = xxxx.o lib_SOURCE = xxxx.c KDIR := /lib/modules/$(shell uname -r)/build/include X86_ASMDIR := /lib/modules/$(shell uname -r)/build/arch/x86/include CONFIG_FILE := /lib/modules/$(shell uname -r)/build/include/generated/autoconf.h LIB = xxxx.a xxxx.a:$(lib_OBJECTS) $(AR) $(ARFLAG) -o $@ $^ $(lib_OBJECTS):$(lib_SOURCE) # $(CC) $(CCFLAGS) -o $@ $^ $(CC) $(CCFLAGS) -I$(KDIR) -I$(X86_ASMDIR) -include $(CONFIG_FILE) -o $@ $^ clean: $(RM) $(lib_OBJECTS.o) $(RM) $(LIB)
Note:其中CONFIG_FILE一项在不同的内核中会有不同
有些版本中是/lib/modules/$(shell uname -r)/build/include/linux/autoconf.h
更高的版本中是/lib/modules/$(shell uname -r)/build/include/linux/kconfig.h
这样编译出的静态库链接生成的驱动在本地可以正常运行,但将这个静态库拿到其他版本内核的linux系统上链接生成的驱动运行时就会导致死机。究其原因,本地编译出的静态库usb_drv_lib.a中的大量的底层接口都是来自本地内核,不同版本内核很多接口实现还是不一样的,虽然用另外一个版本编译出来的静态库可以链接生成驱动usb_drv.ko,却不能正常工作。
如此看来只能提取出与内核无关的代码编译到静态库中了。接下来就是如何提取这些内核无关代码了。
1.将编译usb_drv_lib.a的Makefile_lib改为:
CC = gcc AR = ar CCFLAGS = -c -mcmodel=kernel ARFLAGS = -rc RM = rm -f TARGET = usb_drv_lib.a OBJECTS = usb_drv_lib.o SOURCE = usb_drv_lib.c KERNEL_DIR = /lib/modules/$(shell uname -r)/build CCFLAGS += -I$(KERNEL_DIR)/include ##CCFLAGS += -I$(KERNEL_DIR)/arch/x86/include ##CCFLAGS += -I$(KERNEL_DIR)/include/asm/mach-default #CCFLAGS += -I$(KERNEL_DIR)/include/uapi ##CCFLAGS += -I$(KERNEL_DIR)/arch/x86/include/generated ##CCFLAGS += -include $(KERNEL_DIR)/include/generated/autoconf.h ##CCFLAGS += -include $(KERNEL_DIR)/include/linux/autoconf.h #CCFLAGS += -include $(KERNEL_DIR)/include/linux/kconfig.h #CCFLAGS += -D__KERNEL__ -DKBUILD_MODNAME=\"tailm_drv_lib\" .PHONY : all clean all: $(TARGET) $(TARGET):$(OBJECTS) $(AR) $(ARFLAGS) -o $@ $^ cp $(TARGET) ../ $(OBJECTS):$(SOURCE) $(CC) $(CCFLAGS) -o $@ $^ clean: $(RM) $(TARGET) $(RM) $(OBJECTS)
2.将原来文件usb_drv.c中有个自定义device结构体:
struct usb_stuff { struct usb_device * udev; struct usb_interface * interface; struct semaphore oper_sem; unsigned char * bulk_out_buffer; unsigned char * bulk_in_buffer; size_t bulk_in_size; __u8 bulk_in_endpointAddr; __u8 bulk_out_endpointAddr; struct kref kref; };
改到usb_drv.h中:
struct usb_stuff { void *udev; void *interface; void *psem; unsigned char *bulk_out_buffer; unsigned char *bulk_in_buffer; size_t bulk_in_size; __u8 bulk_in_endpointAddr; __u8 bulk_out_endpointAddr; struct kref kref; };
Note:本来是把struct kref改成void*的,但这个行不通。(具体原因见kref)
由于kref必须保留,所以我在usb_drv_lib.c中包含“#include <linux/kref.h>”。但编译报错,"undefined "atomic_t""。在“#include <linux/types.h>”依然不行。没有深究,直接把kref在usb_drv_lib.c中定义一遍。(mark一下)
typedef struct { volatile int counter; } atomic_t; struct kref { atomic_t refcount; }; //#include <linux/kref.h>
3.将与内核无关的实现都放到静态库中实现。如把usb_drv.c中的read接口:
ssize_t Drv_Read(struct file *file, char *buffer, size_t count, loff_t *ppos) { ...... }
改成
static ssize_t Drv_Read(struct file *file, char *buffer, size_t count, loff_t *ppos) { struct usb_stuff *dev = (struct usb_stuff *)file->private_data; return xxx_Drv_Read(dev, buffer, count, ppos); }
其中ssize_t xxx_Drv_Read(struct usb_stuff *pdev, char *buffer, size_t count, loff_t *ppos)在参与编译静态库usb_drv_lib.a的.c文件usb_drv_lib.c中实现,在usb_drv.h中申明。
4.修改驱动编译的Makefile:
...... drv-objs := usb_drv.o tailm_drv_lib.a ......
Note:这里“drv-objs”提供给kernel中的Makefile使用,但并不是固定的名字,它是${driver_name}-obj。我们的驱动名字叫drv,所以这里就是drv-objs,如果我们的驱动是tailm_drv,那么它就是tailm_drv-objs了。
至此大功告成,真正的read,write实现代码都编译到静态库肿了,我们只需要向用户提供usb_drv.c(基本是个空壳),usb_drv.h,usb_drv_lib.a和Makefile。
(完)