第七章 驱动编译进内核
1 将驱动编译进内核的步骤
将驱动编译成了一个*.ko,独立于zImage的,是一个独立的module,驱动使用的时候就可以安装,不用的时候可以卸载。
将驱动编译进zImage,在zImage启动的过程中,自动的安装该驱动。
1.1 将驱动的原文件,放到内核源码包中去
在drivers/char创建一个目录 led
将led_drv.c拷贝到drivers/char/led/下
1.2 在drivers/char/led/下创建Kconfig
填写如下内容:
menu "leds driver for GEC210"
config GEC210_LEDS
tristate "GEC210 leds driver"
default y
depends on CPU_S5PV210
help
this is a driver for LEDs on GEC210
endmenu
分析:
1)菜单有名称,menu开始,endmenu结束
2)config GEC210_LEDS
定义一个条件编译选项GEC210_LEDS。我们在做make menuconfig,对条件编译选项进行赋值(Y M N),
赋完值后的条件编译选项存放在.config中。条件编译选项给Makefile来使用,确定驱动源码如何编译。
3)tristate "GEC210 leds driver"
条件编译选项的值有三种选择:Y M N
4)default y
条件编译选项的默认值
5)depends on CPU_S5PV210
条件编译选项的依赖,只有选中的依赖(CPU_S5PV210=y),才有可能对这个条件编译选项进行赋值。
1.3 在drivers/char/led/下创建Makefile
makefile是根据条件编译选项的值,对驱动源码如何进行处理。
填写如下内容:
obj-$(CONFIG_GEC210_LEDS) += led_drv.o
1.4 修改上一级目录的Kconfig
drivrs/char/Kconfig中加入:
source "drivers/char/led/Kconfig"
1.5 修改上一级目录的Makefile
drivrs/char/Makefile中加入:
obj-$(CONFIG_GEC210_LEDS) += led/
1.6 make menuconfig
Device Drivers --->
Character devices --->
leds driver for GEC210 --->
<*> GEC210 leds driver (NEW)
1.7 编译
#make
CC drivers/char/led/led_drv.o
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
1.8 下载zImage到GEC210,并启动zImage
zImage的启动输出:
[ 1.042413] s3cfb s3cfb: registered successfully
[ 1.243565] leds driver for GEC210
[ 1.247087] PA FB = 0x45FAF000, bits per pixel = 32
1.9 查看设备文件
[root@GEC210 /]# ls /dev/led_drv -l
crw-rw---- 1 root root 253, 0 Jan 1 13:35 /dev/led_drv
A代码一 驱动编译进内核
1. Filename: led_drv.c
/*LED1 --- GPJ2_0 --- S5PV210_GPJ2(0)
LED2 --- GPJ2_1 --- S5PV210_GPJ2(1)
LED3 --- GPJ2_2 --- S5PV210_GPJ2(2)
LED4 --- GPJ2_3 --- S5PV210_GPJ2(3)*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
//1)定义一个字符设备cdev
static struct cdev led_drv;
static unsigned int led_major = 0; //0-->动态分配,>0-->静态注册
static unsigned int led_minor = 0;
static dev_t led_drv_num;
static struct class *gec210_led_class;
static struct device *gec210_led_device;
struct led_gpio{
unsigned int gpio_num;
char gpio_name[12];
};
static struct led_gpio gec210_leds[4] = {
{
.gpio_num = S5PV210_GPJ2(0),
.gpio_name = "GPJ2_0-LED1",
},
{
.gpio_num = S5PV210_GPJ2(1),
.gpio_name = "GPJ2_1-LED2",
},
{
.gpio_num = S5PV210_GPJ2(2),
.gpio_name = "GPJ2_2-LED2",
},
{
.gpio_num = S5PV210_GPJ2(3),
.gpio_name = "GPJ2_3-LED4",
},
};
//3)定义文件操作集,并初始化
//char buf[2],buf[1]灯的状态:1--on,0-->off
// buf[0]哪一个led:1/2/3/4
ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
int ret;
char kbuf[2];
if(len != 2)
return -EINVAL;
ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据
if(ret!= 0)
return -EFAULT;
if( (kbuf[0]<1) || (kbuf[0]>4) )
return -EINVAL;
if(kbuf[1]==1)
gpio_set_value(gec210_leds[kbuf[0]-1].gpio_num, 0);
else if(kbuf[1]==0)
gpio_set_value(gec210_leds[kbuf[0]-1].gpio_num, 1);
else
return -EINVAL;
return len;
}
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
.write = gec210_led_write,
};
static int __init gec210_led_init(void) //驱动的初始化及安装函数
{
int ret;
int i;
//2)申请/注册设备号
if(led_major == 0){
ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");
}
else{
led_drv_num = MKDEV(led_major,led_minor);
ret = register_chrdev_region(led_drv_num, 1, "gec210_leds");
}
if(ret < 0){
printk("led_drv_num is error \n");
return ret;
}
//4)初始化cdev
cdev_init(&led_drv, &gec210_led_fops);
//5)将cdev加入kernel
ret = cdev_add(&led_drv,led_drv_num, 1 );
if(ret < 0){
printk("cdev add error\n");
goto failed_cdev_add;
}
//6)创建class
gec210_led_class = class_create(THIS_MODULE, "led_class");
if(gec210_led_class == NULL)
{
printk("class create error\n");
ret = -EBUSY;
goto failed_class_create;
}
//7)创建device
gec210_led_device = device_create(gec210_led_class,NULL,
led_drv_num,NULL,"led_drv"); // /dev/led_drv
if(gec210_led_device == NULL)
{
printk("class device error\n");
ret = -EBUSY;
goto failed_device_create;
}
for(i=0;i<4;i++)
{
ret = gpio_request(gec210_leds[i].gpio_num, gec210_leds[i].gpio_name);
if(ret < 0)
{
printk("gpio request error %s\n", gec210_leds[i].gpio_name);
goto failed_gpio_request;
}
gpio_direction_output(gec210_leds[i].gpio_num,0x1);
}
return 0;
failed_gpio_request:
while(i--)//--i
gpio_free(gec210_leds[i].gpio_num);
device_destroy(gec210_led_class,led_drv_num);
failed_device_create:
class_destroy(gec210_led_class);
failed_class_create:
cdev_del(&led_drv);
failed_cdev_add:
unregister_chrdev_region(led_drv_num, 1);
return ret;
}
static void __exit gec210_led_exit(void) //驱动卸载函数
{
int i;
unregister_chrdev_region(led_drv_num, 1);
cdev_del(&led_drv);
device_destroy(gec210_led_class,led_drv_num);
class_destroy(gec210_led_class);
for(i=0;i<4;i++)
gpio_free(gec210_leds[i].gpio_num);
printk("good bye gec210\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//--------------------------------------------------------
2. Filename: test.c
#include
#include
int main(void)
{
int fd;
int ret;
char buf[2];
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/led_drv", O_WRONLY);
if(fd <0)
{
perror("open led_drv:");
return -1;
}
while(1)
{
buf[1] = 1;buf[0]=3; //led3 on
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
buf[1] = 0;buf[0]=3; //led3 on
ret = write(fd,buf,sizeof(buf));
if(ret < 0)
{
perror("write led_drv: ");
return -1;
}
sleep(1);
}
close(fd);
return 0;
}
//--------------------------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
A代码二 蜂鸣器控制
1. Filename: buzzer_drv.test.c
#include
#include
#include
#include
#include
#include
#include
int main (int argc,char **argv)
{
int fd ,ret,err;
fd = open ("/dev/buzzer_drv",O_RDWR);
if (fd == -1)
{
perror ("open failed");
return -1;
}
while (1)
{
printf ("please enter:\n");
scanf ("%d",&ret);
getchar ();
err = write (fd,&ret,sizeof (int));
if (err == -1)
{
perror ("write failed");
return -1;
}
printf ("wirte : %d byte\n",err);
printf ("write : %d\n",ret);
}
close (fd);
return 0;
}
//---------------------------------------
2. Filename: char_drv.c
#include
#include
#include
#include
#include
#include
#include
#include
static struct cdev buzzer_drv; //定义字符设备
static unsigned int buzzer_major = 200; //主设备号若是0,是动态申请;若不是,则默认是静态注册
static unsigned int buzzer_minor = 0; //次设备号
static dev_t buzzer_drv_num; //设备号
static struct class *class; //类
static struct device *device; //设备名称
static struct resource *buzzer_res; //申请物理内存
static unsigned long *GPD0CON; //存放物理内存映射后的虚拟地址
static unsigned long *GPD0DAT;
static int buzzer_open (struct inode *inode,struct file *filp)
{
printk ("ready to open\n");
*GPD0CON &= ~(0xf<<0);
*GPD0CON |= (0x1<<0); //输出状态
*GPD0DAT &= ~(0xf<<0); //高电平,蜂鸣器不响
return 0;
}
static int buzzer_write (struct file *filp,const char __user *buf,size_t len,loff_t *ppos)
{
int err,ret;
printk ("ready to write\n");
err = copy_from_user(&ret,buf,len);
if (err != 0)
{
printk ("copy from user failed\n");
return -EFAULT;
}
printk ("%d\n",ret);
/*根据用户程序决定蜂鸣器情况*/
*GPD0DAT ^= (ret<<0);
return 0;
}
static int buzzer_release (struct inode *inode, struct file *filp)
{
printk ("release success\n");
return 0;
}
/*定义文件操作集*/
static struct file_operations buzzer_drv_fops = {
.owner = THIS_MODULE,
.open = buzzer_open,
.write = buzzer_write,
.release = buzzer_release,
};
/*驱动初始化及安装函数*/
static int __init buzzer_init (void)
{
int err;
buzzer_drv_num = MKDEV (buzzer_major,buzzer_minor);//驱动设备的设备号
/*注册设备*/
err = register_chrdev_region (buzzer_drv_num,1,"buzzer_drv");
if (err < 0)
{
printk ("get buzzer_drv_num failed\n");
return err;
}
/*初始化字符设备*/
cdev_init (&buzzer_drv,&buzzer_drv_fops);
/*字符设备添加至内核*/
err = cdev_add (&buzzer_drv,buzzer_drv_num,1);
if (err < 0)
{
printk ("add failed\n");
goto add_failed;
}
/*自动创建设备文件*/
/*注册一个类*/
class = class_create (THIS_MODULE,"buzzer_drv");
if (IS_ERR(class))
{
printk ("create class failed\n");
goto add_failed;
}
/*从属的类下,在/dev目录下创建设备节点*/
device = device_create (class,NULL,buzzer_drv_num,NULL,"buzzer_drv");
if (IS_ERR(device))
{
printk ("create device failed\n");
goto device_failed;
}
printk ("init success\n");
/*led灯初始化*/
/*申物理内存区域*/
buzzer_res = request_mem_region(0xe02000A0,8,"buzzer_drv");
if (buzzer_res == NULL)
{
printk ("request memory failed\n");
goto request_failed;
}
/*映射虚拟内存*/
GPD0CON =(unsigned long *) ioremap (0xe02000A0,8);
if (GPD0CON == NULL)
{
printk ("ioremap failed\n");
goto ioremap_failed;
//return -EFAULT;
}
GPD0DAT = GPD0CON + 0x1;
printk ("buzzer init success\n");
return 0;
ioremap_failed:
/*释放物理内存空间*/
release_mem_region (0xe02000A0,8);
request_failed:
/*销毁设备节点*/
device_destroy (class,buzzer_drv_num);
device_failed:
/*销毁类*/
class_destroy(class);
add_failed:
/*删除字符设备*/
cdev_del (&buzzer_drv);
/*注销设备号*/
unregister_chrdev_region (buzzer_drv_num,1);
return -1;
}
/*驱动卸载*/
static void __exit buzzer_exit (void)
{
/*取消IO内存映射*/
iounmap (GPD0CON);
/*释放物理内存空间*/
release_mem_region (0xe02000A0,8);
/*销毁设备节点*/
device_destroy (class,buzzer_drv_num);
/*清除类*/
class_destroy (class);
/*删除字符设备*/
cdev_del (&buzzer_drv);
/*注销设备号*/
unregister_chrdev_region (buzzer_drv_num,1);
printk ("exit success\n");
}
/*驱动入口*/
module_init (buzzer_init);
/*驱动出口*/
module_exit (buzzer_exit);
/*内核模块描述,使用#modinfo查看相应信息*/
MODULE_AUTHOR ("yuan");
MODULE_LICENSE ("GPL");
//--------------------------------------------
3. Filename: Makefile
obj-m += char_drv.o
KERNELDIR := /opt/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
第八章 IOCTL的使用
1 IOCTL的使用举例
1.1 应用程序访问显卡
#include
struct fb_fix_screeninfo finfo;
struct fb_fix_screeninfo finfo;
fd = open ("/dev/fb0",O_RDWR);
//FBIOGET_FSCREENINFO-->读取显卡中的液晶屏的固定信息(命令)
//&finfo ---读到的信息放的位置
ioctl(fd,FBIOGET_FSCREENINFO,&finfo)
printf("The mem is :%d\r\n",finfo.smem_len);
printf("The line_length is :%d\r\n",finfo.line_length);
//想fb驱动发送命令FBIOGET_VSCREENINFO,读取液晶屏的可变信息
ioctl(fp,FBIOGET_VSCREENINFO,&vinfo)
printf("The xres is :%d\r\n",vinfo.xres);
printf("The yres is :%d\r\n",vinfo.yres);
printf("bits_per_pixel is :%d\r\n",vinfo.bits_per_pixel);
总结:应用程序可以通过ioctl向驱动程序发送命令,然后再从驱动程序中读取数据。
1.2 ADC的驱动
#define ADC_INPUT_PIN _IOW('S', 0x0c, unsigned long)
static const struct file_operations s3c_adc_fops = {
.owner = THIS_MODULE,
.read = s3c_adc_read,
.open = s3c_adc_open,
.ioctl = s3c_adc_ioctl,
};
static int s3c_adc_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case ADC_INPUT_PIN: //应用程序发下来的命令
adc_port = (unsigned int) arg; //驱动程序得到应用程序写来的arg,ADC的转换通道
return 0;
default:
return -ENOIOCTLCMD;
}
}
1.3对应这个驱动程序的应用程序:
#include
#define ADC_INPUT_PIN _IOW('S', 0x0c, unsigned long)
fd = open("/dev/adc",O_RDWR)
ioctl(fd,ADC_INPUT_PIN, 0)
read(fd, , ) ---->分析adc驱动的read函数
close(fd)
2 用户空间与内核空间的ioctl函数
用户空间ioctl() ----->系统调用 ----->内核空间ioctl()
2.1 用户空间的ioctl()
#include
原型
int ioctl(int fd, int cmd);
int ioctl(int fd, int cmd, unsigned long arg);
参数说明:
int fd ---> 文件描述符
int cmd ---> 应用程序向驱动程序发送的命令
unsigned int arg ---> 应用程序与驱动程序之间交互的参数(双向的)
返回值:
成功,返回0
失败,返回负数的错误码
2.2 内核空间的ioctl()
原型
int ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
long unlocked_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
参数说明:
unsigned int cmd --->应用程序传下来的命令
unsigned long arg --->应用程序与驱动程序之间交互的数据
区别:
与linux内核的版本有关,早期版本file_operations只有ioctl(),中间过渡版本包含了ioctl()和unlocked_ioctl(),
在2.6.36以后的版本,支持支unlocked_ioctl()。取消了内核中的一个叫内核锁。在驱动设计过程中,这个函数是没有区别的。
3 ioctl命令
#define ADC_INPUT_PIN _IOW('S', 0x0c, unsigned long)
IOCTL的命令是由函数生成的,并不是随便选的值1/2/3/4/5/6......。
提示:
应用程序向驱动程序发送命令cmd=2,cmd就传不到驱动程序。中间被系统调用过滤掉了。我们应该使用cmd的标准定义方法。
生成命令的函数:
_IO(type,nr) --->应用程序向驱动程序发送命令,不发送参数
_IOR(type,nr,size) --->应用程序向驱动程序发送命令,从驱动程序中获取参数
_IOW(type,nr,size) --->应用程序向驱动程序发送命令,向驱动程序中写入参数
参数说明:
type ---> 命令的类型(魔数),相当于一个秘钥,该秘钥一般是一个ASCII码值。
nr ---> 序号,该魔数下,命令的需要,是一个整型值。0 1 2 3 4
size ---> 传递参数的大小,函数中写的是参数的类型
/***************
魔数和魔字符串 --->就是指在代码中出现但没有解释的数字常量或字符串,魔数是稳定核素"幻数"的另称
很多类型的文件,其起始的几个字节的内容是固定的(或是有意填充,或是本就如此)。因此这几个字节的内容也被称为魔数 (magic number),因为根据这几个字节的内容就可以确定文件类型。
如果在某个程序中你使用了魔数,那么在几个月(或几年)后你将很可能不知道它的含义是什么。
在一个Project里面,避免使用魔数(Magic Number)和魔字符串(Magic String)是相当必要的。通过定义的常量去access特定的字符串和数字也已经是软件开发的standard。
****************/
命令的组成:方向(2bits),魔数(8bits) 序号(8bits) 参数大小(14bits)
例:
#define GEC210_LED_ON _IOW('L', 0x01, unsigned long)
#define GEC210_LED_OFF _IOW('L', 0x02, unsigned long)
A作业
1.使用ioctl实现蜂鸣器的驱动
2.使用read实现按键的驱动
3.使用ioctl实现按键的驱动
/drivers/char/watchdog/s3c2410_wdt.c
B 代码一
1. Filename: led_drv.c
/*LED1 --- GPJ2_0 --- S5PV210_GPJ2(0)
LED2 --- GPJ2_1 --- S5PV210_GPJ2(1)
LED3 --- GPJ2_2 --- S5PV210_GPJ2(2)
LED4 --- GPJ2_3 --- S5PV210_GPJ2(3)*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GEC210_LED_ON _IOW('L', 0x01, unsigned long)
#define GEC210_LED_OFF _IOW('L', 0x02, unsigned long)
//1)定义一个字符设备cdev
static struct cdev led_drv;
static unsigned int led_major = 0; //0-->动态分配,>0-->静态注册
static unsigned int led_minor = 0;
static dev_t led_drv_num;
static struct class *gec210_led_class;
static struct device *gec210_led_device;
struct led_gpio{
unsigned int gpio_num;
char gpio_name[12];
};
static struct led_gpio gec210_leds[4] = {
{
.gpio_num = S5PV210_GPJ2(0),
.gpio_name = "GPJ2_0-LED1",
},
{
.gpio_num = S5PV210_GPJ2(1),
.gpio_name = "GPJ2_1-LED2",
},
{
.gpio_num = S5PV210_GPJ2(2),
.gpio_name = "GPJ2_2-LED2",
},
{
.gpio_num = S5PV210_GPJ2(3),
.gpio_name = "GPJ2_3-LED4",
},
};
//3)定义文件操作集,并初始化
// cmd是灯的状态,arg哪一个led:1/2/3/4
static long gec210_led_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
switch (cmd) {
case GEC210_LED_ON:
gpio_set_value(gec210_leds[arg-1].gpio_num, 0);
return 0;
case GEC210_LED_OFF:
gpio_set_value(gec210_leds[arg-1].gpio_num, 1);
return 0;
default:
return -ENOIOCTLCMD;
}
}
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = gec210_led_ioctl,
};
static int __init gec210_led_init(void) //驱动的初始化及安装函数
{
int ret;
int i;
//2)申请/注册设备号
if(led_major == 0){
ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");
}
else{
led_drv_num = MKDEV(led_major,led_minor);
ret = register_chrdev_region(led_drv_num, 1, "gec210_leds");
}
if(ret < 0){
printk("led_drv_num is error \n");
return ret;
}
//4)初始化cdev
cdev_init(&led_drv, &gec210_led_fops);
//5)将cdev加入kernel
ret = cdev_add(&led_drv,led_drv_num, 1 );
if(ret < 0){
printk("cdev add error\n");
goto failed_cdev_add;
}
//6)创建class
gec210_led_class = class_create(THIS_MODULE, "led_class");
if(gec210_led_class == NULL)
{
printk("class create error\n");
ret = -EBUSY;
goto failed_class_create;
}
//7)创建device
gec210_led_device = device_create(gec210_led_class,NULL,
led_drv_num,NULL,"led_drv"); // /dev/led_drv
if(gec210_led_device == NULL)
{
printk("class device error\n");
ret = -EBUSY;
goto failed_device_create;
}
for(i=0;i<4;i++)
{
ret = gpio_request(gec210_leds[i].gpio_num, gec210_leds[i].gpio_name);
if(ret < 0)
{
printk("gpio request error %s\n", gec210_leds[i].gpio_name);
goto failed_gpio_request;
}
gpio_direction_output(gec210_leds[i].gpio_num,0x1);
}
return 0;
failed_gpio_request:
while(i--)//--i
gpio_free(gec210_leds[i].gpio_num);
device_destroy(gec210_led_class,led_drv_num);
failed_device_create:
class_destroy(gec210_led_class);
failed_class_create:
cdev_del(&led_drv);
failed_cdev_add:
unregister_chrdev_region(led_drv_num, 1);
return ret;
}
static void __exit gec210_led_exit(void) //驱动卸载函数
{
int i;
unregister_chrdev_region(led_drv_num, 1);
cdev_del(&led_drv);
device_destroy(gec210_led_class,led_drv_num);
class_destroy(gec210_led_class);
for(i=0;i<4;i++)
gpio_free(gec210_leds[i].gpio_num);
printk("good bye gec210\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//-------------------------------------------
2. Filename: test
#include
#include
#include
#define GEC210_LED_ON _IOW('L', 0x01, unsigned long)
#define GEC210_LED_OFF _IOW('L', 0x02, unsigned long)
int main(void)
{
int fd;
int ret;
char buf[2];
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/led_drv", O_WRONLY);
if(fd <0)
{
perror("open led_drv:");
return -1;
}
while(1)
{
ret = ioctl(fd,GEC210_LED_ON,4);
if(ret < 0)
{
perror("ioctl led_drv: ");
return -1;
}
sleep(1);
ret = ioctl(fd,GEC210_LED_OFF,4);
if(ret < 0)
{
perror("ioctl led_drv: ");
return -1;
}
sleep(1);
}
close(fd);
return 0;
}
//-------------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
第九章 混杂设备
1什么是混杂设备
混杂设备(miscdevice)又叫做杂项设备,是普通的字符设备(cdev)的封装,用来降低驱动的设计难度,使一个驱动的设计过程更加简单。
本意:有些驱动,又像块设备,又像字符设备,可以用混杂设备来设计。混杂设备也是一个字符设备,但是对字符设备进行了封装。
主设备号是10.
crw-rw---- 1 root root 10, 131 Jan 1 15:54 adc
crw-rw---- 1 root root 10, 52 Jan 1 15:54 humidity
crw-rw---- 1 root root 10, 254 Jan 1 15:54 s3c-jpg
crw-rw---- 1 root root 10, 252 Jan 1 15:54 s3c-mfc
# cat /proc/misc
52 humidity
53 timer_irq
54 pwm
131 adc
55 network_throughput
56 network_latency
57 cpu_dma_latency
58 device-mapper
242 CEC
243 HPD
240 sec-g2d
254 s3c-jpg
252 s3c-mfc
59 alarm
1 psaux
60 mtp_usb
61 android_adb_enable
62 android_adb
134 apm_bios
63 ashmem
# cat /proc/devices
Character devices:
10 misc
使用misc device的时候,不需要创建class和device,可以自动生成设备文件。
class已经创建好的 ---> /sys/class/misc/
device --->创建混杂设备的时候,就会创建一个device。
2 混杂设备的使用
#include
2.1 定义混杂设备
static struct miscdevice s3c_adc_miscdev = {
.minor = ADC_MINOR, //混杂设备的次设备号,系统自动分配:MISC_DYNAMIC_MINOR
.name = "adc", //misc device的名字,也是设备文件的名字
.fops = &s3c_adc_fops, //文件操作集
};
2.2 将混杂设备注册到内核
//A zero is returned on success and a negative errno code for failure
int misc_register(struct miscdevice * misc)
2.3 注销一个混杂设备
int misc_deregister(struct miscdevice *misc)
A 代码一
1. Filename: led_drv.c
/*LED1 --- GPJ2_0 --- S5PV210_GPJ2(0)
LED2 --- GPJ2_1 --- S5PV210_GPJ2(1)
LED3 --- GPJ2_2 --- S5PV210_GPJ2(2)
LED4 --- GPJ2_3 --- S5PV210_GPJ2(3)*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GEC210_LED_ON _IOW('L', 0x01, unsigned long)
#define GEC210_LED_OFF _IOW('L', 0x02, unsigned long)
struct led_gpio{
unsigned int gpio_num;
char gpio_name[12];
};
static struct led_gpio gec210_leds[4] = {
{
.gpio_num = S5PV210_GPJ2(0),
.gpio_name = "GPJ2_0-LED1",
},
{
.gpio_num = S5PV210_GPJ2(1),
.gpio_name = "GPJ2_1-LED2",
},
{
.gpio_num = S5PV210_GPJ2(2),
.gpio_name = "GPJ2_2-LED2",
},
{
.gpio_num = S5PV210_GPJ2(3),
.gpio_name = "GPJ2_3-LED4",
},
};
//3)定义文件操作集,并初始化
// cmd是灯的状态,arg哪一个led:1/2/3/4
static long gec210_led_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
switch (cmd) {
case GEC210_LED_ON:
gpio_set_value(gec210_leds[arg-1].gpio_num, 0);
return 0;
case GEC210_LED_OFF:
gpio_set_value(gec210_leds[arg-1].gpio_num, 1);
return 0;
default:
return -ENOIOCTLCMD;
}
}
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = gec210_led_ioctl,
};
static struct miscdevice s3c_adc_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "led_drv", //misc device的名字,也是设备文件的名字
.fops = &gec210_led_fops, //文件操作集
};
static int __init gec210_led_init(void) //驱动的初始化及安装函数
{
int ret;
int i;
for(i=0;i<4;i++)
{
ret = gpio_request(gec210_leds[i].gpio_num, gec210_leds[i].gpio_name);
if(ret < 0)
{
printk("gpio request error %s\n", gec210_leds[i].gpio_name);
goto failed_gpio_request;
}
gpio_direction_output(gec210_leds[i].gpio_num,0x1);
}
ret = misc_register(&s3c_adc_miscdev);
if(ret < 0)
{
printk("misc register error\n");
goto failed_misc_register;
}
return 0;
failed_misc_register:
failed_gpio_request:
while(i--)//--i
gpio_free(gec210_leds[i].gpio_num);
return ret;
}
static void __exit gec210_led_exit(void) //驱动卸载函数
{
int i;
for(i=0;i<4;i++)
gpio_free(gec210_leds[i].gpio_num);
misc_deregister(&s3c_adc_miscdev);
printk("good bye gec210\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//------------------------------------
2. Filename: test.c
#include
#include
#include
#define GEC210_LED_ON _IOW('L', 0x01, unsigned long)
#define GEC210_LED_OFF _IOW('L', 0x02, unsigned long)
int main(void)
{
int fd;
int ret;
char buf[2];
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/led_drv", O_WRONLY);
if(fd <0)
{
perror("open led_drv:");
return -1;
}
while(1)
{
ret = ioctl(fd,GEC210_LED_ON,4);
if(ret < 0)
{
perror("ioctl led_drv: ");
return -1;
}
sleep(1);
ret = ioctl(fd,GEC210_LED_OFF,4);
if(ret < 0)
{
perror("ioctl led_drv: ");
return -1;
}
sleep(1);
}
close(fd);
return 0;
}
//----------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
//--------------------------------------
A 代码二
1. Filename: led_drv.c
//蜂鸣器驱动
//BEEP --- GPD0_0 --- S5PV210_GPD0(0)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GEC210_BEEP_ON _IO('L', 0x01)
#define GEC210_BEEP_OFF _IO('L', 0x02)
#define BEEP_GPIO S5PV210_GPD0(0) //
static const char beep_name[]="gpd0_0_beep";
//3)定义文件操作集,并初始化
//cmd是灯的状态,arg哪一个led:1/2/3/4
static long gec210_beep_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
switch (cmd) {
case GEC210_BEEP_ON:
gpio_set_value(BEEP_GPIO, 1);
return 0;
case GEC210_BEEP_OFF:
gpio_set_value(BEEP_GPIO, 0);
return 0;
default:
return -ENOIOCTLCMD;
}
}
static struct file_operations gec210_beep_fops = { //结构体
.owner = THIS_MODULE,
.unlocked_ioctl = gec210_beep_ioctl,
};
static struct miscdevice s3c_beep_miscdev = { //结构体
.minor = MISC_DYNAMIC_MINOR,
.name = "beep_drv", //misc device的名字,也是设备文件的名字
.fops = &gec210_beep_fops, //文件操作集
};
static int __init gec210_beep_init(void) //驱动的初始化及安装函数
{
int ret;
ret = gpio_request(BEEP_GPIO, beep_name);
if(ret < 0)
{
printk("gpio request error %s\n", beep_name);
goto failed_gpio_request;
}
gpio_direction_output(BEEP_GPIO,0x0); //初始为关,即加载驱动时为不响
ret = misc_register(&s3c_beep_miscdev); //注册
if(ret < 0)
{
printk("misc register error\n");
goto failed_misc_register;
}
return 0;
failed_misc_register:
failed_gpio_request:
gpio_free(BEEP_GPIO);
return ret;
}
static void __exit gec210_beep_exit(void) //驱动卸载函数
{
gpio_free(BEEP_GPIO);
misc_deregister(&s3c_beep_miscdev);
printk("good bye gec210\n");
}
module_init(gec210_beep_init); //驱动的入口
module_exit(gec210_beep_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//------------------------------------
2. Filename: test.c
#include
#include
#include
#define GEC210_BEEP_ON _IO('L', 0x01) //
#define GEC210_BEEP_OFF _IO('L', 0x02) //
int main(void)
{
int fd;
int ret;
char buf[2];
//"/dev/led_drv" ---linux驱动的设备文件节点(node)
fd = open("/dev/beep_drv", O_WRONLY); //打开
if(fd <0)
{
perror("open led_drv:");
return -1;
}
while(1)
{
ret = ioctl(fd, GEC210_BEEP_ON);//开,可有两个参数,也可有三个参数
if(ret < 0)
{
perror("ioctl led_drv: ");
return -1;
}
sleep(1);//开1秒,即响一秒
ret = ioctl(fd, GEC210_BEEP_OFF);//关,
if(ret < 0)
{
perror("ioctl led_drv: ");
return -1;
}
sleep(3); //间隔,停3秒,
}
close(fd); //关闭
return 0;
}
//----------------------------------------
3. Filename: Makefile
obj-m += beep_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
第十章 linux中的内存分配
1 MMU 内存管理单元
1.1 什么是MMU
MMU --- memory management unit 内存管理单元
MMU --- 是ARM内核中的一个硬件模块,不是一个软件协议包。
1.2 MMU的作用
1.2.1将虚拟地址转换成物理地址
1.2.2设置地址的访问属性:只读 可读写 禁止访问的。
一般比较复杂的操作系统,运行的时候都需要使用MMU:嵌入式linux wince vxworks .....
不需要MMU:uclinux uC/OS-II (实时性较好)
1.3 MMU将虚拟地址转换成物理地址的单位
映射的单位:
1.3.1 段 section:1MB --->GEC210的uboot
1.3.2大页 large page 64KB
1.3.3小页 small page 4KB --->linux内核 (page)
1.3.4极小页 tiny page 1kB
1.4 MMU如何做地址转换(以“段”来举例)
MMU将虚拟地址转换成物理地址的中心思想-----查表。描述一个MMU的表主要包含两个内容,分别是:
1.4.1表的索引 --->虚拟地址
1.4.2表的内容 --->物理地址,和该地址区的访问属性。
分析:物理地址空间(虚拟地址空间)大小是4GB,段的大小是1MB,所以表的索引值有4k个。
即1MB为单位进行地址映射,最多需要4KB页表项(条目,entry)。
表的索引值是虚拟地址的高12bits,即VA[31:20]
页表的内容是物理地址的高12bits即PA[31:20]和访问属性
问:
MMU如果通过页表来进行虚拟地址转换成物理地址的呢???
答:
CPU使用一个32bits的虚拟地址VA[31:0],改虚拟地址送给MMU,MMU使用VA[31:20]来查找页表,找到页表中对应的条目,并且读出条目的内容,该内容是:PA[31:20]和访问属性。PA[19:0] = VA[19:0].
1.5 页表
1.5.1页表是在开启MMU之前初始化好的,是我们做初始化的。
1.5.2页表的索引是虚拟地址,页表的内容是物理地址和访问属性。所以虚拟地址和物理地址的关系是我们定的。
1.5.3 MMU查表的过程是一个硬件自动查表的过程,用户不需要干预。
1.5.4页表要放在DDR2内存中,应为我们可以在系统运行过程中,改写这个页表
1.5.5虚拟地址 = ioremap(物理地址) --->改写MMU页表的过程,重新确定虚拟地址和物理地址之间的关系。
ioremap() --->io内存的重新映射。
补充资料:S3C2410的使用手册--附录3
嵌入式linux应用开发完全手册 --->第7章 内存管理单元MMU
2 内存的分配
2.1 用户空间内存分配方法
#include
void *malloc(size_t size);
void free(void *ptr);
2.2 内核空间的分配内存方法
2.2.1 kmalloc
原型
#include
void *kmalloc(size_t size, gfp_t flags)
参数说明:
size_t size --->分配内存的大小(字节数),不能超过128KB
gfp_t flags --->分配内存的标志,常用的分配标志
GFP_ATOMIC --->分配内存的过程是一个“原子过程”,该过程不能被打断。
GFP_DMA --->给DMA控制器分配内存,使用该标志,确保分配的物理地址和虚拟地址都是是连续的。
GFP_KERNEL --->内存的正常分配。
void kfree(const void *objp)
kmalloc()的变形:
void *kzalloc(size_t size, gfp_t flags)
void kfree(const void *objp)
2.2.2 vmalloc
原型
void *vmalloc(unsigned long size)
void vfree(const void *addr)
linux内核的启动输出:
[ 0.000000] Virtual kernel memory layout:
[ 0.000000] vector : 0xffff0000 - 0xffff1000 ( 4 kB)--->中断向量表
[ 0.000000] fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB) --->固定映射区
[ 0.000000] DMA : 0xff000000 - 0xffe00000 ( 14 MB) --->DMA内存区 GFP_DMA
[ 0.000000] vmalloc : 0xe0800000 - 0xfc000000 ( 440 MB) --->vmalloc分配的内存区
[ 0.000000] lowmem : 0xc0000000 - 0xe0000000 ( 512 MB) --->正常分配的内存区 GFP_KERNEL
[ 0.000000] modules : 0xbf000000 - 0xc0000000 ( 16 MB) --->安装的ko
[ 0.000000] .init : 0xc0008000 - 0xc008e000 ( 536 kB)
[ 0.000000] .text : 0xc008e000 - 0xc07d5000 (7452 kB)
[ 0.000000] .data : 0xc07d6000 - 0xc08345a0 ( 378 kB)
2.3 kmalloc和vmalloc的区别
2.3.1分配内存的位置不一样
kmalloc在lowmem区,而vmalloc在vmalloc区。
2.3.2分配内存的大小不同
kmalloc不能超过128kB,而vmalloc没有限制
2.3.3如果给DMA控制器来分配内存,只能使用kmalloc(, GFP_DMA)
2.3.4分配内存的过程是一个原子过程,只能使用kmalloc(,GFP_ATOMIC)
2.3.5 kmalloc可以保证分配内存的物理地址和虚拟地址都是连续的,但是vmalloc不能保证物理地址是连续的。
第十一章 linux中的中断
1中断的使用
#include
1.1 申请中断
原型
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
参数说明:
unsigned int irq --->中断号,每个中断源都有一个中断号
irq_handler_t handler --->中断源对应的中断服务程序
unsigned long flags --->中断的标志
const char *name --->自定义的中断名称
void *dev --->向中端服务程序传递的参数
返回值:
成功,返回0
失败,返回负数的错误码
1.2 中断服务程序
原型
irqreturn_t (*irq_handler_t)(int irq, void *dev)
{
return IRQ_HANDLED;
}
参数说明:
int irq --- 响应该中断服务程序的中断号
void *dev ---注册中断的时候,使用的一个参数
1.3 释放中断
原型
void free_irq(unsigned int irq, void *dev_id)
参数说明:
int irq --- 注册该中断服务程序的中断号
void *dev ---注册中断的时候,使用的一个参数
1.4 中断号
每个中断源都有一个中断号,因为中断源是与硬件相关的,所以中断号应该是在具体的硬件平台(具体的CPU)中定义。
1) UART的中断号
#define IRQ_S5P_UART_RX0 (IRQ_S5P_UART_BASE0 + UART_IRQ_RXD)
#define IRQ_S5P_UART_TX0 (IRQ_S5P_UART_BASE0 + UART_IRQ_TXD)
#define IRQ_S5P_UART_ERR0 (IRQ_S5P_UART_BASE0 + UART_IRQ_ERR)
#define IRQ_S5P_UART_RX1 (IRQ_S5P_UART_BASE1 + UART_IRQ_RXD)
#define IRQ_S5P_UART_TX1 (IRQ_S5P_UART_BASE1 + UART_IRQ_TXD)
#define IRQ_S5P_UART_ERR1 (IRQ_S5P_UART_BASE1 + UART_IRQ_ERR)
#define IRQ_S5P_UART_RX2 (IRQ_S5P_UART_BASE2 + UART_IRQ_RXD)
#define IRQ_S5P_UART_TX2 (IRQ_S5P_UART_BASE2 + UART_IRQ_TXD)
#define IRQ_S5P_UART_ERR2 (IRQ_S5P_UART_BASE2 + UART_IRQ_ERR)
#define IRQ_S5P_UART_RX3 (IRQ_S5P_UART_BASE3 + UART_IRQ_RXD)
#define IRQ_S5P_UART_TX3 (IRQ_S5P_UART_BASE3 + UART_IRQ_TXD)
#define IRQ_S5P_UART_ERR3 (IRQ_S5P_UART_BASE3 + UART_IRQ_ERR)
2) PWM定时器的中断号
#define IRQ_TIMER0 S5P_TIMER_IRQ(0)
#define IRQ_TIMER1 S5P_TIMER_IRQ(1)
#define IRQ_TIMER2 S5P_TIMER_IRQ(2)
#define IRQ_TIMER3 S5P_TIMER_IRQ(3)
#define IRQ_TIMER4 S5P_TIMER_IRQ(4)
3) 外部中断的中断号
#define IRQ_EINT(x) ((x) < 16 ? ((x) + S5P_EINT_BASE1) \
: ((x) - 16 + S5P_EINT_BASE2))
x=0~31
1.5 中断的标志
相当于是中断的处理方法或者中断的触发方式
1) 外部中断
IRQF_TRIGGER_RISING
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_HIGH
IRQF_TRIGGER_LOW
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
2) 内部中断
IRQF_DISABLED ---> 相应当前中断的时候,关闭其他中断的相应
IRQF_SHARED --->当一个中断源多次注册的时候,使用该关键字
IRQF_TIMER --->申请一个定时器中断
2 查看安装后的驱动
# cat /proc/interrupts
CPU0
16: 1291 s3c-uart s5pv210-uart
18: 509 s3c-uart s5pv210-uart
39: 0 s5p_vic_eint eth0
45: 1 s5p_vic_eint hpd
46: 0 s5p_vic_eint ft5x0x_ts
50: 0 VIC s3c-pl330.0
51: 0 VIC s3c-pl330.1
52: 0 VIC s3c-pl330.2
58: 0 VIC System timer
61: 23368 VIC rtc-tick
78: 141 VIC s3c2440-i2c.0
83: 1 VIC s3c2440-i2c.2
87: 26 VIC ehci_hcd:usb1, ohci_hcd:usb2
88: 1 VIC s3c-udc
90: 0 VIC mmc0
91: 0 VIC mmc1
92: 13 VIC mmc2
93: 0 VIC s3c-csis
97: 8181 VIC s3cfb
101: 0 VIC s3c-fimc0
102: 0 VIC s3c-fimc1
103: 0 VIC s3c-fimc2
104: 0 VIC s3c-jpg
105: 0 VIC s3c-g2d
107: 0 VIC s5p-tvout
108: 0 VIC s5p-tvout
109: 0 VIC s3c2440-i2c.1
110: 0 VIC s3c-mfc
111: 0 VIC s5p-tvout
130: 13 VIC mmc3
131: 0 VIC s5p-cec
160: 0 s5p-eint key2_eint16
中断号 中断发生的次数 中断控制器 中断的名称
3多个中断源可以共用一个中断服务程序
irqreturn_t (*irq_handler_t)(int irq, void *dev)
{
//1 int irq --->来确定中断源
//2 void *dev --->来确定中断源
return IRQ_HANDLED;
}
4等待队列
我们可以定义一个等待队列,并给这个等待队列设置一个等待的条件,让进程访问等待队列的时候,首先判断等待条件,
条件为真 --->直接越过等待队列,继续向下运行。
条件为假 --->进程就会产生阻塞,就会进入睡眠状态,睡眠在等待队列。当条件为真的时候,我们可以唤醒等待队列中睡眠的进程,让这个进程继续运行。
等待队列的使用方法
#include
4.1定义一个等待队列头
4.1.1静态的定义及初始化
DECLARE_WAIT_QUEUE_HEAD(name)
例:
DECLARE_WAIT_QUEUE_HEAD(gec210_key_wq)
4.1.2动态的定义及初始化
定义:
static wait_queue_head_t gec210_key_wq;
初始化:
init_waitqueue_head(wait_queue_head_t *q)
例:
static wait_queue_head_t gec210_key_wq;
init_waitqueue_head(&gec210_key_wq);
4.2判断等待条件
wait_event(wait_queue_head_t q, int condition);
wait_event_interruptible(wait_queue_head_t q, int condition)
4.3唤醒等待队列中的进程
wake_up(wait_queue_head_t *q)
wake_up_interruptible(wait_queue_head_t *q)
5等待队列程序调试
5.1 安装驱动
5.2 执行应用程序(后台)
./test&
5.3 top
Mem: 12208K used, 332692K free, 0K shrd, 0K buff, 4832K cached
CPU: 0.0% usr 0.0% sys 0.0% nic 100% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 0.00 0.00 0.00 1/41 152
PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND
152 67 root R 2260 0.6 0 0.0 top
67 1 root S 2260 0.6 0 0.0 -/bin/sh
1 0 root S 2256 0.6 0 0.0 init
151 67 root S 1576 0.4 0 0.0 ./test
52 2 root SW 0 0.0 0 0.0 [yaffs-bg-1]
5 2 root SW 0 0.0 0 0.0 [events/0]
12 2 root SW 0 0.0 0 0.0 [khubd]
2 0 root SW 0 0.0 0 0.0 [kthreadd]
3 2 root SW 0 0.0 0 0.0 [ksoftirqd/0]
4 2 root SW 0 0.0 0 0.0 [watchdog/0]
6 2 root SW 0 0.0 0 0.0 [khelper]
7 2 root SW 0 0.0 0 0.0 [async/mgr]
8 2 root SW 0 0.0 0 0.0 [suspend]
9 2 root SW 0 0.0 0 0.0 [sync_supers]
10 2 root SW 0 0.0 0 0.0 [bdi-default]
11 2 root SW 0 0.0 0 0.0 [kblockd/0]
13 2 root SW 0 0.0 0 0.0 [kseriod]
14 2 root SW 0 0.0 0 0.0 [kmmcd]
15 2 root SW 0 0.0 0 0.0 [cfg80211]
16 2 root SW 0 0.0 0 0.0 [rpciod/0]
进程test的状态是S --- sleep
A作业一:
1 采用静态定义等待队列的方法,定义一个等待队列。
2 判断等待条件的时候:wait_event(wait_queue_head_t q, int condition);
3 唤醒等待队列的时候:wake_up(&gec210_key_wq);
中断看进程的状态:
4 总结等待队列的使用方法
A作业二:
1. 设计Key2~Key5四个按键的驱动,这个外部中断使用一个中断服务程序
2.
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), &__key); \
} while (0)
改成1:
#define init_waitqueue_head(q) \
{ \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), &__key); \
}
使用do { }while(0),宏定义函数,有什么特点???
B代码一:
1. Filename: led_drv.c
#include
#include
#include
irqreturn_t key2_eint16_isr(int irq, void *dev)
{
printk("key2 is pressing, irq = %d\n",irq);
return IRQ_HANDLED;
}
static int __init gec210_key_init(void) //驱动的初始化及安装函数
{
int ret;
ret = request_irq(IRQ_EINT(16), key2_eint16_isr, IRQF_TRIGGER_FALLING,
"key2_eint16", NULL);
if(ret < 0)
{
printk("request irq error\n");
return ret;
}
printk("hello gec210\n"); //替代printf()
return 0;
}
static void __exit gec210_key_exit(void)
{
free_irq(IRQ_EINT(16), NULL);
printk("good bye gec210\n");
}
module_init(gec210_key_init); //驱动的入口
module_exit(gec210_key_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//---------------------------------------
2. Filename: test.c
//---------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
B代码二:
1. Filename: led_drv.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int num[4]={2,3,4,5};
struct led_gpio{
unsigned int gpio_num;
char gpio_name[12];
};
static struct led_gpio gec210_leds[4] = {
{
.gpio_num = S5PV210_GPJ2(0),
.gpio_name = "GPJ2_0-LED1",
},
{
.gpio_num = S5PV210_GPJ2(1),
.gpio_name = "GPJ2_1-LED2",
},
{
.gpio_num = S5PV210_GPJ2(2),
.gpio_name = "GPJ2_2-LED2",
},
{
.gpio_num = S5PV210_GPJ2(3),
.gpio_name = "GPJ2_3-LED4",
},
};
irqreturn_t key_eint_isr(int irq , void *dev) //void *dev 来区分不同的中断源
{
static volatile int led_flag = 1;
led_flag = !led_flag;
printk("key[%d] is pressed,irq is %d!\n",*((int *)dev),irq);
gpio_set_value(gec210_leds[*((int *)dev)-2].gpio_num,led_flag);
//gpio_set_value(gec210_leds[*((int *)dev)-2].gpio_num,1);
return IRQ_HANDLED;
}
static struct file_operations gec210_led_fops = {
.owner = THIS_MODULE,
};
static struct miscdevice s3c_adc_miscdev = {
.minor = MISC_DYNAMIC_MINOR, //混杂设备的次设备号,系统自动分配:MISC_DYNAMIC_MINOR
.name = "led_drv", //misc device的名字,也是设备文件的名字
.fops = &gec210_led_fops, //文件操作集
};
static int __init gec210_key_init(void) //驱动的初始化及安装函数
{
int ret;
int i;
for(i=0;i<4;i++)
{
ret = request_irq(IRQ_EINT(i+16),key_eint_isr,IRQF_TRIGGER_FALLING,"key_eint",num+i);
if(ret < 0)
{
printk("failed to request irq!\n");
goto failed_request_irq;
}
}
for(i=0;i<4;i++)
{
ret = gpio_request(gec210_leds[i].gpio_num, gec210_leds[i].gpio_name);
if(ret < 0)
{
printk("gpio request error %s\n", gec210_leds[i].gpio_name);
goto failed_gpio_request;
}
gpio_direction_output(gec210_leds[i].gpio_num,0x1);
}
ret = misc_register(&s3c_adc_miscdev);
if(ret < 0)
{
printk("misc register error\n");
goto failed_misc_register;
}
printk("hello gec210\n"); //替代printf()
return 0;
failed_request_irq:
while(i--)
free_irq(IRQ_EINT(i+16),num+i);
failed_gpio_request:
failed_misc_register:
while(i--)
gpio_free(gec210_leds[i].gpio_num);
return ret;
}
static void __exit gec210_key_exit(void)
{
int i;
for(i=16;i<20;i++)
free_irq(IRQ_EINT(i),num-16+i);
for(i=0;i<4;i++)
gpio_free(gec210_leds[i].gpio_num);
misc_deregister(&s3c_adc_miscdev);
printk("good bye gec210\n");
}
module_init(gec210_key_init); //驱动的入口
module_exit(gec210_key_exit); //驱动的出口
//内核模块的描述
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("the first demo of module");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//---------------------------------------
2. Filename: test.c
#include
unsigned int flag =1;
int main(void)
{
flag ^= 1;
printf("1. %d\n", flag);
flag = !1;
printf("2. %d\n", flag);
flag =~1;
printf("3. %u\n", flag);
return 0;
}
//---------------------------------------
3. Filename: Makefile
obj-m += led_drv.o
#KERNELDIR := /lib/modules/$(shell uname -r)/build
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko
B 代码三:
1. Filename: key_drv.c
//当有按键按下,我们就可以读按键的状态;没有按键按下,进程就睡眠在驱动的read函数中。#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//1.定义个等待队列头
static wait_queue_head_t gec210_key_wq;
//3.定义一个等待队列的条件
static int gec210_key_flag = 0; //有按键按下为真(1),没有按键按下为假(0)
//定义一个buffer,存放按键的状态
static char key_buf[3] = {0,0,0}; //k2 K3 K4 没有按下
struct irq_info
{
char irq_name[10];
int irq_num;
};
struct irq_info key_irq_info[3] = {
{
.irq_name = "key2_irq",
.irq_num = IRQ_EINT(16),
},
{
.irq_name = "key3_irq",
.irq_num = IRQ_EINT(17),
},
{
.irq_name = "key4_irq",
.irq_num = IRQ_EINT(18),
},
};
ssize_t gec210_key_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
int ret,i;
//4.判断等待条件,有按键按下,就继续读;没有按键按下,就阻塞,进程进入等待队列睡眠。
wait_event_interruptible(gec210_key_wq, gec210_key_flag);
if(len != 3)
return -EINVAL;
ret = copy_to_user(buf,key_buf,len);
if(ret < 0)
return -EFAULT;
gec210_key_flag = 0; //重置等待条件
for(i=0;i<3;i++)
key_buf[i] = 0;
return len;
}
static struct file_operations gec210_key_fops = {
.owner = THIS_MODULE,
.read = gec210_key_read,
};
static struct miscdevice gec210_key_miscdev = {
.minor = MISC_DYNAMIC_MINOR, //混杂设备的次设备号,系统自动分配:MISC_DYNAMIC_MINOR
.name = "key_drv", //misc device的名字,也是设备文件的名字
.fops = &gec210_key_fops, //文件操作集
};
static irqreturn_t gec210_key_isr(int irq, void *dev)
{
printk("<0>""---------irq: %d isr -------- \n", irq);
if(irq == key_irq_info[0].irq_num)
{
key_buf[0] = 1;//KEY2按下
}
else if(irq == key_irq_info[1].irq_num)
{
key_buf[1] = 1;//KEY3按下
}
else if(irq == key_irq_info[2].irq_num)
{
key_buf[2] = 1;//KEY4按下
}
//5.当条件满足后,唤醒等待队列中的进程。
gec210_key_flag = 1; //当有按键按下的时候,条件设置成真
wake_up_interruptible(&gec210_key_wq);
return IRQ_HANDLED; // 表示isr已经正常执行了
}
static int __init gec210_key_irq_init(void)
{
int ret, i;
//2.初始化等待队列头
init_waitqueue_head(&gec210_key_wq);
for(i=0; i<3; i++)
{
ret = request_irq(key_irq_info[i].irq_num, gec210_key_isr,
IRQF_TRIGGER_RISING, key_irq_info[i].irq_name, NULL);
if(ret)
{
printk("request irq error \n");
goto failed_request_irq;
}
}
ret = misc_register(&gec210_key_miscdev);
if(ret < 0)
{
printk("misc register error\n");
goto failed_misc_register;
}
printk("gec210 key irq init success ! \n");
return 0;
failed_misc_register:
failed_request_irq:
while(i--)
free_irq(key_irq_info[i].irq_num, NULL);
return ret;
}
static void __exit gec210_key_irq_exit(void)
{
int i;
for(i=0; i<3; i++)
{
free_irq(key_irq_info[i].irq_num, NULL);
}
misc_deregister(&gec210_key_miscdev);
printk("gec210 key irq exit success ! \n");
}
module_init(gec210_key_irq_init);
module_exit(gec210_key_irq_exit);
MODULE_AUTHOR("lllllssssssssjjjjjjjjjjjjjjj");
MODULE_DESCRIPTION("the first demo of irq device");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
//---------------------------------------
2. Filename: test.c
#include
#include
char key_status[3] = {0,0,0};
int main(void)
{
int fd,i,ret;
fd = open("/dev/key_drv", O_RDONLY);
if(fd < 0)
{
perror("open /dev/key_drv");
return -1;
}
while(1)
{
ret = read(fd, key_status, 3);
if(ret != 3)
{
perror("read");
return -1;
}
for(i=0;i<3;i++)
{
printf("key_status[%d] = %d\n",i,key_status[i]);
}
}
close(fd);
return 0;
}
//---------------------------------------
3. Filename: Makefile
obj-m += key_drv.o
KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110
PWD:=$(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko