现在的位置: 首页 > 操作系统 > 正文

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

2019年06月14日 操作系统 ⁄ 共 7076字 ⁄ 字号 评论关闭

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。

(完)

抱歉!评论已关闭.