初学驱动编译,各种不懂啊,记得有些东西曾经看到过有印象,但是还是不清晰,原因就是基础不牢固,动手太少,中间又学学停停。内容多不要紧,重复是最好的老师!坚持能进步!记录一下最近一周的收获。
一、内核的编译
分为为当前使用的系统编译内核和为嵌入式单板编译内核
参考: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实现的