初学Linux驱动编译

       初学驱动编译,各种不懂啊,记得有些东西曾经看到过有印象,但是还是不清晰,原因就是基础不牢固,动手太少,中间又学学停停。内容多不要紧,重复是最好的老师!坚持能进步!记录一下最近一周的收获。

一、内核的编译

分为为当前使用的系统编译内核和为嵌入式单板编译内核

参考:http://blog.csdn.net/crazycoder8848/article/details/44131735

1、准备工作

  a、安装虚拟机,在虚拟机上安装Linux

  b、安装GCC,用来编译;安装make、ncurses等工具

  c、下载纯净的Linux内核源码包

  d、如果是为移植Linux到嵌入式系统,还要安装交叉编译工具链

2、设置编译目标

在配置或编译内核之前先确定CPU的类型和编译时用什么交叉编译链

这里以arm为例。有两种方法

a)修改Makefile(推荐此法)

打开内核源码根目录下的Makefile,修改如下两个Makefile变量,并保存。

ARCH  :=arm

CROSS_COMPILE := arm-linux-

b)每次执行make时,通过命令行参数传入这些信息

3、配置内核

内核的功能很多,根据我们的需求进行裁剪编译,需要哪些部分,每部分要编译成什么形式的,每部分的工作参数是如何的,这些都是可以配置的。在开始编译之前我们需要一份配置清单,放到内核源码根目录下,命名为.config文件,然后根据此.config文件编译出我们的内核。

但是内核的配置项太多了,而且不同的CPU架构所配置的项集合是不一样的,所以内核提供了一种简单的配置方法。

以arm为例,做法如下:

a) 从/arch/arm/configs/找到相似的配置文件xxx_deconfig,拷贝到内核源码根目录下,命名为.config文件(也可以在内核源码根目录下执行make xxx_defconfig,生成.config文件)。如果是给当前使用的PC机编译内核,可以使用当前内核目录的.config文件

b) 执行make menuconfig ,对此配置做一些需要的修改,将新的配置更新到.config文件中(内核打开了一组配置项集合,各层的kconfig文件)

即使不需要对配置做任何的修改,都务必执行一次make menuconfig,进入配置界面后直接退出并保存。

4、编译

make

5、内核编译机制

.config设置了将某个功能编译进内核,某个功能编译成模块,设置某个功能的参数,定义了Makefile变量。在make刚开始编译的时候编译系统还会生成2个config文件,分别是include/config/auto.config和include/linux/autoconf.h。顶层的Makefile将包含auto.conf,这样获得Makefile的变量,从而知道如何编译内核的各个部分。make工具带着这些Makefile变量一层一层的进入各子系统或者模块中去给子Makefile。而内核源码.c文件包含了autoconf.h文件,就知道用户有木有配置某功能了。


二、驱动开发环境的建立

同样也分为为当前使用的系统使用和为嵌入式单板编译驱动

为嵌入式单板编译的话,需要在当前使用的Linux系统中,下载和该嵌入式单板一样配置环境的内核(相同的内核源码、库文件、和各种内核配置等),编译一次。然后将编译好的.ko文件通过ftp或者NFS到嵌入式单板中去

三、驱动的编译

分为独立编译和将将模块代码集成到内核源码中跟随Linux内核一起编译两种。后者就和编译内核一样了。

四、led驱动的制作

a) 直接使用file_operation结构体或者使用cdev结构体及其相关的初始化函数

b)使用write()函数或者ioctl()函数

c)通过给每个led编号和次设备号都可以实现对每个led的操作

下面是led_drv.c

#include
#include  
#include
#include
#include
#include
#include
#include
#include
#include
#include
/********************************
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
********************************/
//#define DEVICE_NAME "led_drv1"
int major;
static struct class *leds_class;
static struct class_device *leds_class_devs[4];

static char leds_status=0x0;
static DECLARE_MUTEX(leds_lock); //定义赋值
/**************
volatile unsigned long *gpbcon = NULL;
volatile unsigned long *gpbdat = NULL;
**************/
static unsigned long gpio_va;
#define GPIO_OFT(x) ((x)-0x56000000) //标准宏定义
#define GPBCON (*(volatile unsigned long *)(gpio_va+GPIO_OFT(0x56000010)))
#define GPBDAT (*(volatile unsigned long *)(gpio_va+GPIO_OFT(0x56000014)))

static int leds_drv_open(struct inode *inode,struct file *file)
{
int minor=MINOR(inode->i_rdev);
switch(minor)
{
case 0: /*/dev/leds_tal*/
{
//s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP); /** 系统内核提供的封装好的函数 **/
GPBCON &= ~(0x3<<(5*2)); //清零
GPBCON |= (0x1<<(5*2)); //设置为输出

//s3c2410_gpio_cfgpin(S3C2410_GPB6,S3C2410_GPB6_OUTP);
GPBCON &= ~(0x3<<(6*2)); //清零
GPBCON |= (0x1<<(6*2)); //设置为输出

//s3c2410_gpio_cfgpin(S3C2410_GPB7,S3C2410_GPB7_OUTP);
GPBCON &= ~(0x3<<(7*2)); //清零
GPBCON |= (0x1<<(7*2)); //设置为输出

//s3c2410_gpio_cfgpin(S3C2410_GPB8,S3C2410_GPB8_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB8,1);

//s3c2410_gpio_setpin(S3C2410_GPB5,0);
/* open函数里面为什么要给0/1赋值?应该是初始化 */
GPBDAT &= (0x1<<5);

//s3c2410_gpio_setpin(S3C2410_GPB6,0);
GPBDAT &= (0x1<<6);

//s3c2410_gpio_setpin(S3C2410_GPB7,0);
GPBDAT &= (0x1<<7);

down(&leds_lock);
leds_status=0x0;
up(&leds_lock);

break;
}
case 1: /* /dev/leds_1 */
{
s3c2410_gpio_cfgpin(S3C2410_GPB5,S3C2410_GPB5_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB5,0);

down(&leds_lock);
leds_status&=~(0x1<<0);
up(&leds_lock);

break;
}
case 2: /* /dev/leds_2 */
{
s3c2410_gpio_cfgpin(S3C2410_GPB6,S3C2410_GPB6_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB6,0);

down(&leds_lock);
leds_status&=~(0x1<<1);
up(&leds_lock);

break;
}
case 3: /* /dev/leds_3 */
{
s3c2410_gpio_cfgpin(S3C2410_GPB7,S3C2410_GPB7_OUTP);
//s3c2410_gpio_setpin(S3C2410_GPB7,0);

down(&leds_lock);
leds_status&=~(0x1<<2);
up(&leds_lock);

break;
}
}
/* 配置 GPBIO 5、6、7、为输出*/
/**********************************************
*gpbcon&=~((0x3<<(5*2))|(0x3<<(6*2))|(0x3<<(7*2)));
*gpbcon|=((0x1<<(5*2))|(0x1<<(6*2))|(0x1<<(7*2)));
************************************************/
return 0;
}

static ssize_t leds_drv_read(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
int minor=MINOR(file->f_dentry->d_inode->i_rdev);
char val;

switch(minor)
{
case 0: /*/dev/leds_tal*/
{
copy_to_user(buf,(const void *)&leds_status,1);
break;
}
case 1: /* /dev/leds_1 */
{
down(&leds_lock);
val=leds_status&0x1;
up(&leds_lock);
copy_to_user(buf,(const void *)&val,1);
break;
}
case 2: /* /dev/leds_2 */
{
down(&leds_lock);
val=(leds_status>>1)&0x1;
up(&leds_lock);
copy_to_user(buf,(const void *)&val,1);
break;
}
case 3: /* /dev/leds_3 */
{
down(&leds_lock);
val=(leds_status>>2)&0x1;
up(&leds_lock);
copy_to_user(buf,(const void *)&val,1);
break;
}
}
return 1;
}

static ssize_t leds_drv_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{

/* GPBIO 5、6、7、 的高低电平数据 */
/******************
int val;
copy_from_user(&val,buf,count); //copy_to_user();
if(val==1)
{

*gpbdat&=~((0x1<<5)|(0x1<<6)|(0x1<<7));//灭灯
}
else
{
*gpbdat|=(0x1<<5)|(0x1<<6)|(0x1<<7);//点灯
}
***********/

int minor=MINOR(file->f_dentry->d_inode->i_rdev);
char val;

copy_from_user(&val,buf,1);
switch(minor)
{
case 0: /* /dev/leds */
{
s3c2410_gpio_setpin(S3C2410_GPB5,(val&0x1));
s3c2410_gpio_setpin(S3C2410_GPB6,(val&0x1));
s3c2410_gpio_setpin(S3C2410_GPB7,(val&0x1));
down(&leds_lock);
leds_status=val;
up(&leds_lock);
break;
}
case 1: /* /dev/leds_1 */
{
s3c2410_gpio_setpin(S3C2410_GPB5,val);
if(val == 0)
{
down(&leds_lock);
leds_status&=~(1<<0);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status|=(1<<0);
up(&leds_lock);
}
break;
}
case 2: /* /dev/leds_2 */
{
s3c2410_gpio_setpin(S3C2410_GPB6,val);
if(val == 0)
{
down(&leds_lock);
leds_status&=~(1<<1);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status|=(1<<1);
up(&leds_lock);
}
break;
}
case 3: /* /dev/leds_3 */
{
s3c2410_gpio_setpin(S3C2410_GPB7,val);
if(val == 0)
{
down(&leds_lock);
leds_status&=~(1<<2);
up(&leds_lock);
}
else
{
down(&leds_lock);
leds_status|=(1<<2);
up(&leds_lock);
}
break;
}
}
return 1;
}

static struct file_operations leds_drv_fops= /* 这是定义一个结构体变量first_drv_fops */
{
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的 */
.open = leds_drv_open,
.read = leds_drv_read,
.write = leds_drv_write,
};

static int __init leds_drv_init(void) //驱动的入口函数,调用注册函数
{
int ret;
int minor = 0;

gpio_va = (volatile unsigned long)ioremap(0x56000000,16);
if(!gpio_va)
{
return -EIO ;
}

/*注册字符设备,参数为设备号、设备名字、file_operation结构
*这样,将定义的这个结构体变量告诉内核,主设备号就可以和这个结构联系起来
*操作主设备led_major的设备文件时,就可以调用file_operation结构中相应的函数
*led_major为0时,内核自动分配主设备号
*/

//major=register_chrdev(0,DEVICE_NAME,&leds_drv_fops);
major=register_chrdev(0,"leds_drv1",&leds_drv_fops);
/* 写零,自动生成设备号,自动创建节点 */
// //注册,

if(major<0)
{
//printk(DEVICE_NAME "can't register major number!\n");
printk("leds_drv1 can't register major number!\n");
return major;
}
leds_class = class_create(THIS_MODULE,"leds_drv1");/* 先创建一个类,联系/sys/class/下的信息 */
if(IS_ERR(leds_class))
return PTR_ERR(leds_class);

/** 在类下创建结点,创建 /dev/leds_tal /dev/leds_1 /dev/leds_2 /dev/leds_3 4个设备 **/
leds_class_devs[0]=
device_create(leds_class,NULL,MKDEV(major,0),NULL,"leds_tal");
for(minor=1;minor<4;minor++)
{
leds_class_devs[minor] =
device_create(leds_class,NULL,MKDEV(major,minor),NULL,"leds_%d",minor);
if(unlikely(IS_ERR(leds_class_devs[minor])))
return PTR_ERR(leds_class_devs[minor]);
}

//printk(DEVICE_NAME "initialized !\n");
printk("leds_drv1 initialized !\n");
return 0;
/****
gpbcon= (volatile unsigned long)ioremap(0x56000010,16);
gpbdat=gpbcon+1; //这里是加1,不是加4
******/
}

void __exit leds_drv_exit(void)
{
int minor;
unregister_chrdev(major,"leds_drv1"); //卸载模块函数
for(minor=0;minor<4;minor++)
{
device_unregister(leds_class_devs[minor]);
}
class_destroy(leds_class);
iounmap(gpio_va);

}

module_init(leds_drv_init);
/*
修饰一下,所谓修饰就是用一个宏来定义一个结构体,这个结构体里面的某个函数指针指向shangmiandehanshu*/
module_exit(leds_drv_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("xiaoyu 937");
MODULE_DESCRIPTION("A simple LEDS_DRV Module ");
MODULE_VERSION("V1.0");

以下是Makefile文件

CURR_DIR:=$(shell pwd)
KERN_DIR:=/home/xy/TQ2440/My_kernel/opt/EmbedSky/linux-2.6.30.4
# when it is ".../EmbedSky/linux-2.6.30.4/kernel",it appeared "nu rule to make target modules"
all:
	make -C $(KERN_DIR) SUBDIRS=$(CURR_DIR) modules 
clean:
	make -C $(KERN_DIR) SUBDIRS=$(CURR_DIR) clean
	rm -rf modules.order
obj-m :=led_drv.o

以下是测试程序

#include 
#include 
#include 
#include 

/*
 * ./led_drv1_test  
 */

void print_usage(char * file)
{
	printf("Usage:\n");
	printf("%s  \n",file);
	printf("eg:\n");
	printf("%s /dev/leds_tal on\n",file);
	printf("%s /dev/leds_tal off\n",file);
	printf("%s /dev/leds_1 on\n",file);
	printf("%s /dev/leds_1 off\n",file);
}

int main(int argc,char **argv)
{
	int fd;
	char *filename;
	char val;

	char buf1;
	if(argc!=3)
	{
		print_usage(argv[0]);
		return 0;
	}

	filename=argv[1];
	fd=open(filename,O_RDWR);
	if(fd<0)
	{
		printf("error! can't open %s\n",filename);
		return 0;
	}
	if(strcmp(argv[2],"on")==0)
	{
		val=0;	//点灯
		write(fd,&val,1);
		read(fd,&buf1,1);
		printf("leds_status is %d \n",buf1);
	}
	else if(strcmp(argv[2],"off")==0)
	{

		val = 1;
		write(fd,&val,1);
		printf("leds_status is %d \n",buf1);
	}
	else 
	{
		print_usage(filename);
	}
	return 0;
}

问题:代码是参考别人的,自己已经理解、编译成功的。但是还是有些地方暂时还不明白:

a) up()和down()信号量机制函数不太明白在这里的用途

b) led_status的作用

五、由于内核版本的不同,一些头文件的路径存在变化,可以再大路径下查找文件的位置

六、杂项设备

参考:http://blog.csdn.net/ssdsafsdsd/article/details/8510948

设备驱动一般分为3种:字符型设备、块设备和网络设备,但是不能将所有的设备全部概括,不属于这3种的成为杂项设备(misc设备,miscellaneous)或者混杂设备。对于杂项设备,Linux内核专门提供了这样一个结构体,其有很强的包容性,如下:

该文件包含在Linux2.6.32.6/include/linux/Miscdevice.h中

struct miscdevice {

int minor;

const char *name;

const struct file_operations *fops;

struct list_head list;

struct device *parent;

struct device *this_device;

const char *nodename;

mode_t mode;

};

同时提供的miscdevice注册和注销函数如下所示。

int misc_register(struct miscdevice * misc);

int misc_deregister(struct miscdevice *misc);

其本质还是仍然还是字符设备,只不过将这种设备驱动增加了一层封装。其主体还是file_operation实现的




你可能感兴趣的:(Linux设备驱动学习,Linux内核)