字符设备驱动开发
字符设备驱动定义准备工作系统整体工作原理代码重要部分分析file_operation结构体register_chrdev函数unregister_chrdev函数
代码编写Makefilemodule_test.capp.c编译module_test,将.ko放入NFS共享目录通过NFS传输 .ko 文件到开发板
安装、查看、卸载
GPIOLIB方式注册重新编译内核代码编写module_test.cmakefile
编译、安装、测试
platform
字符设备驱动
定义
以字节为单位进行操作,如:(LCD、串口、LED、蜂鸣器、触摸屏……)。
准备工作
编译任何驱动程序前,需要先编译内核,因为驱动程序需要用到内核中的一些文件。 原因: 驱动程序要用到内核中的一些文件。 编译驱动时用的内核要与开发板上运行的内核一致。 更换板子上的内核后,板子上其他驱动也要重新编译。
系统整体工作原理
应用层->API->设备驱动->硬件 API:open、read、write、closa等 驱动源码中提供真正的open、read、write、close等函数实体 (此处需要添加一张图片)
代码重要部分分析
file_operation结构体
所在目录:kernel/include/linux/fs.h
struct file_operations
{
struct module
*owner
;
loff_t
(*llseek
) (struct file
*, loff_t
, int);
ssize_t
(*read
) (struct file
*, char __user
*, size_t
, loff_t
*);
ssize_t
(*write
) (struct file
*, const char __user
*, size_t
, loff_t
*);
ssize_t
(*aio_read
) (struct kiocb
*, const struct iovec
*, unsigned long, loff_t
);
ssize_t
(*aio_write
) (struct kiocb
*, const struct iovec
*, unsigned long, loff_t
);
int (*readdir
) (struct file
*, void *, filldir_t
);
unsigned int (*poll
) (struct file
*, struct poll_table_struct
*);
int (*ioctl
) (struct inode
*, struct file
*, unsigned int, unsigned long);
long (*unlocked_ioctl
) (struct file
*, unsigned int, unsigned long);
long (*compat_ioctl
) (struct file
*, unsigned int, unsigned long);
int (*mmap
) (struct file
*, struct vm_area_struct
*);
int (*open
) (struct inode
*, struct file
*);
int (*flush
) (struct file
*, fl_owner_t id
);
int (*release
) (struct inode
*, struct file
*);
int (*fsync
) (struct file
*, int datasync
);
int (*aio_fsync
) (struct kiocb
*, int datasync
);
int (*fasync
) (int, struct file
*, int);
int (*lock
) (struct file
*, int, struct file_lock
*);
ssize_t
(*sendpage
) (struct file
*, struct page
*, int, size_t
, loff_t
*, int);
unsigned long (*get_unmapped_area
)(struct file
*, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags
)(int);
int (*flock
) (struct file
*, int, struct file_lock
*);
ssize_t
(*splice_write
)(struct pipe_inode_info
*, struct file
*, loff_t
*, size_t
, unsigned int);
ssize_t
(*splice_read
)(struct file
*, loff_t
*, struct pipe_inode_info
*, size_t
, unsigned int);
int (*setlease
)(struct file
*, long, struct file_lock
**);
};
作用: 驱动向内核注册时,用于挂接实体函数的地址。 每一个驱动程序代码中都需要该结构体类型的变量。
register_chrdev函数
所在目录:kernel/include/linux/fs.h
static inline int register_chrdev(unsigned int major
, const char *name
, const struct file_operations
*fops
)
{
return __register_chrdev(major
, 0, 256, name
, fops
);
}
作用: 用于驱动向内核注册自己的file_operations。 将要注册的驱动存储在内核驱动数组的相应位置。 补: 查看内核中已经注册的驱动指令
cat
/proc
/devices
unregister_chrdev函数
所在目录:kernel/include/linux/fs.h
static inline void unregister_chrdev(unsigned int major
, const char *name
)
{
__unregister_chrdev(major
, 0, 256, name
);
}
作用: 用于在内核中卸载安装的驱动。
代码编写
Makefile
# 开发板的linux内核的源码树目录
KERN_DIR
= /x210v3_bsp
/qt_x210v3
/kernel
obj
-m
+= module_test
.o
all
:
make
-C $
(KERN_DIR
) M
=`pwd` modules
arm
-none
-linux
-gnueabi
-gcc app
.c
-o app
cp
:
cp
*.ko
/home
/ww
/nfs_rootfs
cp app
/home
/ww
/nfs_rootfs
.PHONY
: clean
clean
:
make
-C $
(KERN_DIR
) M
=`pwd` modules clean
rm
-rf app
module_test.c
该示例为内核自动分配主设备号
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/string.h>
#define MYNAME "chartest"
#define rGPJ0CON *((volatile unsigned int *)S5PV210_GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)S5PV210_GPJ0DAT)
int MYMAJOR
;
char kbuf
[100];
static int test_chrdev_open(struct inode
*inode
, struct file
*file
)
{
printk(KERN_INFO
"test_chrdev_open success\n");
return 0;
}
static int test_chrdev_release(struct inode
*inode
, struct file
*file
)
{
printk(KERN_INFO
"test_chrdev_release success\n");
return 0;
}
static ssize_t
test_chrdev_write(struct file
*file
, const char __user
*ubuf
, size_t count
, loff_t
*ppos
)
{
if (!copy_from_user(kbuf
, ubuf
, count
))
{
printk(KERN_INFO
"test_chrdev_write success\n");
if(kbuf
[0] == '1')
{
rGPJ0DAT
= ((0<<3) | (0<<4) | (0<<5));
}
else if(kbuf
[0] == '0')
{
rGPJ0DAT
= ((1<<3) | (1<<4) | (1<<5));
}
else
{
printk(KERN_INFO
"please input '1' or '0'\n");
}
}
else
{
printk(KERN_INFO
"test_chrdev_write fail\n");
}
return 0;
}
ssize_t
test_chrdev_read(struct file
*file
, char __user
*ubuf
, size_t count
, loff_t
*ppos
)
{
if(!copy_to_user(ubuf
, kbuf
, count
))
{
printk(KERN_INFO
"test_chrdev_read success\n");
}
else
{
printk(KERN_INFO
"test_chrdev_read fail\n");
}
return 0;
}
static const struct file_operations test_fops
={
.owner
= THIS_MODULE
,
.open
= test_chrdev_open
,
.release
= test_chrdev_release
,
.write
= test_chrdev_write
,
.read
= test_chrdev_read
,
};
static int __init
chrdev_init(void)
{
printk(KERN_INFO
"chrdev_init \n");
MYMAJOR
= register_chrdev(0, MYNAME
, &test_fops
);
if(MYMAJOR
< 0)
{
printk(KERN_ERR
"register_chrdev fail\n");
return -EINVAL
;
}
else
{
printk(KERN_INFO
"register_chrdev success\n");
return 0;
}
}
static void __exit
chrdev_exit(void)
{
printk(KERN_INFO
"chrdev_exit \n");
unregister_chrdev(MYMAJOR
, MYNAME
);
}
module_init(chrdev_init
);
module_exit(chrdev_exit
);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("aston");
MODULE_DESCRIPTION("module test");
MODULE_ALIAS("alias xxx");
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE "dev/test"
char buf
[100];
int main(void)
{
int fd
= -1;
int i
= 0;
fd
= open(FILE
, O_RDWR
);
if (fd
< 0)
{
printf("open %s error.\n", FILE
);
return -1;
}
printf("open %s success..\n", FILE
);
while (1)
{
memset(buf
, 0 , sizeof(buf
));
printf("please input on | off | flash | quit | read\n");
scanf("%s", buf
);
if (!strcmp(buf
, "on"))
{
write(fd
, "1", 1);
}
else if (!strcmp(buf
, "off"))
{
write(fd
, "0", 1);
}
else if (!strcmp(buf
, "flash"))
{
for (i
=0; i
<3; i
++)
{
write(fd
, "1", 1);
sleep(1);
write(fd
, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf
, "quit"))
{
break;
}
else if (!strcmp(buf
, "read"))
{
memset(buf
, 0, sizeof(buf
));
read(fd
, buf
, 1);
if(buf
[0] == '1')
{
printf("led is on\n");
}
else if(buf
[0] == '0')
{
printf("led is off\n");
}
else
{
printf("error\n");
}
}
}
close(fd
);
return 0;
}
编译module_test,将.ko放入NFS共享目录
进入到Makefile和module_test.c这两个文件的文件夹目录
cd
/mnt
/hgfs
/share
/x210v3
/5.2.10
make all
make cp
通过NFS传输 .ko 文件到开发板
假设Windows IP为192.168.1.100,在开发板上执行以下命令(注意:必须指定port为2049、 mountport为9999):
mount
-t nfs
-o nolock
,vers
=3,port
=2049,mountport
=9999 192.168.1.100:/home
/ww
/nfs_rootfs
/mnt
安装、查看、卸载
查看内核中已注册的驱动主设备号
cat
/proc
/devices
查看已经安装的驱动
lsmod
查看已有的设备文件
ls
/dev
/ -l
安装驱动
insmod module_test
.ko
查看内核中已注册的驱动主设备号
cat
/proc
/devices
查看已经安装的驱动
lsmod
使用mknod创建设备文件
mknod
/dev
/test c
250 0
查看设备文件
ls
/dev
/test
-l
测试app
./app
先卸载驱动
rmmod module_test
再删除设备文件
rm
-r
/dev
/test
查看内核中已注册的驱动主设备号
cat
/proc
/devices
查看已经安装的驱动
lsmod
GPIOLIB方式注册
重新编译内核
执行 make menuconfig,去掉九鼎之前定义led驱动 选择上内核自己定义的led class 执行 make ,然后重新下载 zImage
代码编写
采用gpiolib的方式来控制led,实际使用的函数 (1)gpio_request: 向内核的gpiolib部分申请,得到允许后才可以去使用这个gpio。 (2)gpio_free: 对应gpio_request,用来释放申请后用完了的gpio (3)gpiochip_is_requested: 接口用来判断某一个gpio是否已经被申请了 (4)gpio_direction_input / gpio_direction_output: 接口用来设置gpio为输入/输出模式 (5)gpio_set_value: 设置gpio输出值 (6)gpio_get_value: 获取gpio值
module_test.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#define GPIO_LED1 S5PV210_GPJ0(3)
static void s5pv210_led_set(struct led_classdev
*led_cdev
, enum led_brightness value
)
{
if(value
== 1)
{
gpio_set_value(GPIO_LED1
, 0);
}
else if(value
== 0)
{
gpio_set_value(GPIO_LED1
, 1);
}
else
{
printk(KERN_INFO
"please input 0 or 1\n");
}
}
static struct led_classdev led1
=
{
.name
= "led1",
.brightness
= 0,
.max_brightness
= 255,
.brightness_set
= s5pv210_led_set
,
};
static int __init
s5pv210_led_init(void)
{
int ret
= -1;
printk(KERN_INFO
"s5pv210_led_init start\n");
ret
= gpio_request(GPIO_LED1
, "led1_gpjo.3");
if (ret
< 0)
{
printk(KERN_INFO
"gpio_register failed\n");
return -EINVAL
;
}
else
{
printk(KERN_INFO
"gpio_register success\n");
gpio_direction_output(GPIO_LED1
, 1);
}
ret
= led_classdev_register(NULL, &led1
);
if (ret
< 0)
{
printk(KERN_INFO
"led_classdev_register failed\n");
return -EINVAL
;
}
else
{
printk(KERN_INFO
"led_classdev_register success\n");
}
return 0;
}
static void __exit
s5pv210_led_exit(void)
{
printk(KERN_INFO
"s5pv210_led_exit start\n");
gpio_free(GPIO_LED1
);
printk(KERN_INFO
"s5pv210_led_exit end\n");
}
module_init(s5pv210_led_init
);
module_exit(s5pv210_led_exit
);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ww");
MODULE_DESCRIPTION("module test");
MODULE_ALIAS("alias xxx");
makefile
# 开发板的linux内核的源码树目录
KERN_DIR
= /x210v3_bsp
/qt_x210v3
/kernel
obj
-m
+= module_test
.o
all
:
make
-C $
(KERN_DIR
) M
=`pwd` modules
cp
:
cp
*.ko
/home
/ww
/nfs_rootfs
.PHONY
: clean
clean
:
make
-C $
(KERN_DIR
) M
=`pwd` modules clean
编译、安装、测试
执行 make 指令,make cp指令,将编译得到的module_test.ko复制到nfs文件夹。 开发板执行nfs指令,传到开发板的 /mnt 目录
mount
-t nfs
-o nolock
,vers
=3,port
=2049,mountport
=9999 192.168.1.100:/home
/ww
/nfs_rootfs
/mnt
安装前先查看开发板原状态
mount
-t debugfs debugfs
/tmp
cat
/tmp
/gpio
使用完后umount /tmp卸载掉debugfs,防止引入错误。
安装驱动查看开发板状态
insmod module_tes
.ko
测试驱动
cd
/sys
/class
/leds
/led1
ls
cat brightness
echo
1 > brightness
cat brightness
led灯亮 再次输入一个其他值,查看结果
从此处可以看出,cat brightness 实际执行的是s5pv210_led_set()函数
platform
static struct S5pv210_led_platdata s5pv210_led1_pdata
= {
.name
= "led1",
.gpio
= S5PV210_GPJ0(3),
.flags
= S5pv210_LEDF_ACTLOW
| S5pv210_LEDF_TRISTATE
,
};
static struct S5pv210_led_platdata s5pv210_led2_pdata
= {
.name
= "led2",
.gpio
= S5PV210_GPJ0(4),
.flags
= S5pv210_LEDF_ACTLOW
| S5pv210_LEDF_TRISTATE
,
};
static struct S5pv210_led_platdata s5pv210_led3_pdata
= {
.name
= "led3",
.gpio
= S5PV210_GPJ0(5),
.flags
= S5pv210_LEDF_ACTLOW
| S5pv210_LEDF_TRISTATE
,
};
struct platform_device s5pv210_led1
= {
.name
= "s5pv210_led",
.id
= 1,
.dev
= { .platform_data
= &s5pv210_led1_pdata
},
};
struct platform_device s5pv210_led2
= {
.name
= "s5pv210_led",
.id
= 2,
.dev
= { .platform_data
= &s5pv210_led2_pdata
},
};
struct platform_device s5pv210_led3
= {
.name
= "s5pv210_led",
.id
= 3,
.dev
= { .platform_data
= &s5pv210_led3_pdata
},
};
static struct platform_device
*smdkc110_devices
[] __initdata
= {
&s5pv210_led1
,
&s5pv210_led2
,
&s5pv210_led3
,
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>
struct s5pv210_gpio_led
{
struct led_classdev cdev
;
struct S5pv210_led_platdata
*pdata
;
};
static inline struct s5pv210_gpio_led
*pdev_to_gpio(struct platform_device
*dev
)
{
return platform_get_drvdata(dev
);
}
static inline struct s5pv210_gpio_led
*to_gpio(struct led_classdev
*led_cdev
)
{
return container_of(led_cdev
, struct s5pv210_gpio_led
, cdev
);
}
static void s5pv210_led_set(struct led_classdev
*led_cdev
, enum led_brightness value
)
{
struct s5pv210_gpio_led
*led
= to_gpio(led_cdev
);
struct S5pv210_led_platdata
*pd
= led
->pdata
;
if(value
== 1)
{
gpio_set_value(pd
->gpio
, 0);
}
else if(value
== 0)
{
gpio_set_value(pd
->gpio
, 1);
}
else
{
printk(KERN_INFO
"please input 0 or 1\n");
}
}
static int s5pv210_led_probe(struct platform_device
*dev
)
{
struct S5pv210_led_platdata
*pdata
= dev
->dev
.platform_data
;
struct s5pv210_gpio_led
*led
;
int ret
;
led
= kzalloc(sizeof(struct s5pv210_gpio_led
), GFP_KERNEL
);
if (led
== NULL)
{
printk(KERN_INFO
"No memory for device\n");
return -ENOMEM
;
}
platform_set_drvdata(dev
, led
);
led
->cdev
.name
= pdata
->name
;
led
->cdev
.brightness
= 0;
led
->cdev
.brightness_set
= s5pv210_led_set
;
led
->pdata
= pdata
;
ret
= led_classdev_register(&dev
->dev
, &led
->cdev
);
if (ret
< 0)
{
printk(KERN_INFO
"led_classdev_register failed\n");
kfree(led
);
return ret
;
}
ret
= gpio_request(pdata
->gpio
, pdata
->name
);
if (ret
< 0)
{
printk(KERN_INFO
"gpio_register failed\n");
}
else
{
printk(KERN_INFO
"gpio_register success\n");
gpio_direction_output(pdata
->gpio
, 1);
}
return 0;
}
static int s5pv210_led_remove(struct platform_device
*dev
)
{
struct s5pv210_gpio_led
*led
= pdev_to_gpio(dev
);
led_classdev_unregister(&led
->cdev
);
gpio_free(led
->pdata
->gpio
);
kfree(led
);
return 0;
}
static struct platform_driver s5pv210_led_driver
= {
.probe
= s5pv210_led_probe
,
.remove
= s5pv210_led_remove
,
.driver
= {
.name
= "s5pv210_led",
.owner
= THIS_MODULE
,
},
};
static int __init
s5pv210_led_init(void)
{
return platform_driver_register(&s5pv210_led_driver
);
}
static void __exit
s5pv210_led_exit(void)
{
platform_driver_unregister(&s5pv210_led_driver
);
}
module_init(s5pv210_led_init
);
module_exit(s5pv210_led_exit
);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ww");
MODULE_DESCRIPTION("module test");
MODULE_ALIAS("alias xxx");