linux usb驱动整理(将部分源码编译成静态库)

来源:互联网 发布:电气设备市场数据 编辑:程序博客网 时间:2024/06/06 00:46

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.0int 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 descriptiontypedef 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.cSRCFOLDER = $(shell pwd)OBJS = $(patsubst %.cpp,%.o, $(CPPS))TARGET_OBJS = $(foreach obj, $(OBJS), $(SRCFOLDER)/$(obj))SOURCE = $(foreach cpp, $(CPPS), $(SRCFOLDER)/$(cpp))TARGET = drvobj-m = $(TARGET).odrv-objs := usb_drv.oCURPATH = $(shell pwd)KERNEL_DIR = /lib/modules/$(shell uname -r)/buildCFLAGS += -mcmodel=kernelPWD = $(shell pwd).PHONY = all load unload reload cleanall : $(TARGET)$(TARGET): $(MAKE) -C  $(KERNEL_DIR) CFLAGS="$(CFLAGS)" M=$(PWD)$(TARGET_OBJS) : $(SOURCE)$(CC) -o $@ $(CFLAGS) -c $<load:insmod $(TARGET).kounload:rmmod $(TARGET).koreload: unload loadclean:@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 -fCCFLAGS = -c -O2 -D__KERNEL__ARFLAG  = -rc CC = gcc -fno-common -vAR = arlib_OBJECTS  =  xxxx.olib_SOURCE  =  xxxx.cKDIR := /lib/modules/$(shell uname -r)/build/includeX86_ASMDIR := /lib/modules/$(shell uname -r)/build/arch/x86/includeCONFIG_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 = gccAR = arCCFLAGS = -c -mcmodel=kernelARFLAGS = -rcRM = rm -fTARGET = usb_drv_lib.aOBJECTS = usb_drv_lib.oSOURCE = usb_drv_lib.cKERNEL_DIR = /lib/modules/$(shell uname -r)/buildCCFLAGS += -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 cleanall: $(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 semaphoreoper_sem;unsigned char *bulk_out_buffer;unsigned char *bulk_in_buffer;size_tbulk_in_size;__u8bulk_in_endpointAddr;__u8bulk_out_endpointAddr;struct krefkref;};
改到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。

(完)


0 0