Linux字符设备驱动——经典标准字符模型
文章目录
- 特征
- 头文件
- 注册函数
- 注销函数
- 设备号,设备驱动节点需要手动创建
- 经典标准字符驱动模型
- 驱动程序zx_chrdev_led.c源代码
- 应用程序app.c源代码
- 实例代码测试
- 测试次设备号是否是全部占用
经典标准字符模型,即为Linux2.6之前的早期经典标准字符模型。
特征
- 没有使用一个核心的结构体,把需要的信息进行封装
- 安装驱动后,不会在/dev/目录下创建设备节点,需要使用mknod创建。
- 一个主设备号只能被注册一次,一次注册0~255的次设备号就被占用了。
杂项设备,每次注册,只占用了一个次设备号,(主设备号固定为10)
头文件
#include
注册函数
注册一个标准字符设备
int register_chrdev(unsigned int major,const char *name, const struct file_operations *fops)
参数:
major: 主设备号
mane: 设备名,不需要和/dev 下对应节点名相同
fops: 文件操作集合
返回值
(1)、当major为0时,表示动态分配
成功:返回分配的主设备号
失败:返回负数
(2)、当major>0时,表示静态注册,使用用户定义的主设备号
成功:返回0
失败:返回负数
注销函数
void unregister_chrdev(unsigned int major, const char *name)
参数
major:主设备号
name:设备名,使用register_chrdev 注册的设备名。
返回值:无
设备号,设备驱动节点需要手动创建
主设备号:0~255,10为杂项设备号
次设备号:0~255
注意:
早期经典字符设备不会在dev目录下创建设备驱动节点,需要用户手动创建,并且注册后占用一个主设备号,因此次设备号被全部占用。
注册字符设备后,都会在/proc/decices目录下出现主设备号。
创建主设备号250,次设备号100的设备节点
mknod /dev/abc c 250 100
打印等级,数字越小,等级越高
查看开发板等级
cat /proc/sys/kernel/prink
早期模型,注册了主设备号,就会占用其下的次设备号。
经典标准字符驱动模型
驱动程序zx_chrdev_led.c源代码
#include <linux/module.h> //驱动模块头文件
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h> //开发板引脚支持头文件
#include <asm/uaccess.h> //copy_from_user ,copy_to_user
#include<linux/gpio.h> // GPIO头文件
#define DEV_NAME "zx_chrdev_led"
#define LED_NUM (4)
static unsigned int LED_pin[LED_NUM] = {
EXYNOS4X12_GPM4(0),
EXYNOS4X12_GPM4(1),
EXYNOS4X12_GPM4(2),
EXYNOS4X12_GPM4(3),
};
static unsigned int major = 0; //动态配置设置0,否则设置正数1~255 (自己保证可用)
static char * pchrdev_name = DEV_NAME;
static ssize_t chrdev_read (struct file *pfile, char __user *buff, size_t size, loff_t *off)
{
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static ssize_t chrdev_write(struct file *filp,
const char __user *user_buf,
size_t count,
loff_t *off)
{
int ret = 0;
char buf[LED_NUM] = {0};
int i = 0;
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
if(count == 0){
return 0;
}
if(count > LED_NUM){ //因为板子只有4盏灯
count = LED_NUM;
}
ret = copy_from_user(buf, user_buf, count); //用户空间传数据到内核空间
if(ret){
return ret;
}
for(i = 0; i < count; i++)
{
if(buf[i] == 1){
gpio_set_value(LED_pin[i],0);/* 亮 */
}else if(buf[i] == 0){
gpio_set_value(LED_pin[i],1);/* 灭 */
}
}
return count;
}
static int chrdev_open(struct inode *pinode, struct file *pfile)
{
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static int chrdev_release(struct inode *pinode, struct file *pfile)
{
printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__);
return 0;
}
static const struct file_operations chrdev_fops = {
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_release,
.open = chrdev_open,
};
static int __init zx_chrdev_led_init(void)
{
int ret;
int i;
for(i = 0;i < LED_NUM; i++)
{
ret = gpio_request(LED_pin[i],"leds");
if(ret < 0){
printk(KERN_EMERG "gpio_request is error\n");
goto gpio_request_err;
}
gpio_direction_output(LED_pin[i],1); //四盏灯设置为输出功能,并且输出高电平
}
if(major){
ret = register_chrdev(major,pchrdev_name,&chrdev_fops); //静态分设备号配方式
if ( ret < 0 ){
printk(KERN_EMERG "register_chrdev error\n");
goto register_chrdev_err;
}else{
major = register_chrdev(0, pchrdev_name, &chrdev_fops); //动态分配设备号方式
if ( major < 0 ){
ret = major;
printk(KERN_EMERG "register_chrdev error\n");
goto register_chrdev_err;
}
}
printk(KERN_EMERG "major:%d\n",major);
printk(KERN_EMERG "%s is OK!!!\n",__FUNCTION__);
return 0;
register_chrdev_err:
gpio_request_err:
for(--i; i >= 0; i--)
{
gpio_free(LED_pin[i]);
}
return ret ;
}
static void __exit zx_chrdev_led_exit(void)
{
int i = 0;
for(i=0;i<4;i++)
{
gpio_set_value(LED_pin[i],1); //灭灯
gpio_free(LED_pin[i]); //释放GPIO
}
unregister_chrdev(major, pchrdev_name);
printk(KERN_EMERG "Goodbye,zx_chrdev_led\n");
}
module_init(zx_chrdev_led_init);
module_exit(zx_chrdev_led_exit);
MODULE_LICENSE("GPL");
应用程序app.c源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEV_NAME "/dev/zx_chrdev_led"
#define LED_NUM (4)
int main(void)
{
char buf[] = {1, 0, 0, 0}; //1£¬ÁÁ£¬0±íʾÃð£¬
int i = 0;
int fd;
fd = open(DEV_NAME, O_RDWR);
if(fd < 0){
printf("open :%s failt!\r\n", DEV_NAME);
}
while(1)
{
write(fd, buf, LED_NUM);
memset(buf, 0, LED_NUM);
buf[i++ % LED_NUM] = 1;
sleep(1);
}
return 0;
}
对应的Makefile只需修改杂项设备对应的Makefile,修改驱动的名字就可以。
实例代码测试
[root@ZX20150811 /home]# ls
app zx_chrdev_led.ko
[root@ZX20150811 /home]# insmod zx_chrdev_led.ko
[ 3643.345000] major:249
[ 3643.345000] zx_chrdev_led_init is OK!!!
[root@ZX20150811 /home]# mknod /dev/zx_chrdev_led c 249 1
[root@ZX20150811 /home]# ./app
[ 3740.410000] line:61,chrdev_open is call
[ 3740.410000] line:36,chrdev_write is call
[ 3741.410000] line:36,chrdev_write is call
[ 3742.410000] line:36,chrdev_write is call
[ 3743.410000] line:36,chrdev_write is call
[ 3744.410000] line:36,chrdev_write is call
[ 3745.410000] line:36,chrdev_write is call
[ 3746.410000] line:36,chrdev_write is call
^C[ 3746.990000] line:68,chrdev_release is call
[root@ZX20150811 /home]#
这里的次设备号为1,因为经典标准字符模型一旦注册了主设备号,这个主设备号下的次设备号都被占用。下面测试其它的次设备号。
测试次设备号是否是全部占用
[root@ZX20150811 /home]# rmmod zx_chrdev_led.ko
[ 3879.105000] Goodbye,zx_chrdev_led
[root@ZX20150811 /home]# rm -f /dev/zx_chrdev_led
[root@ZX20150811 /home]# ls
app zx_chrdev_led.ko
[root@ZX20150811 /home]# insmod zx_chrdev_led.ko
[ 3913.425000] major:249
[ 3913.425000] zx_chrdev_led_init is OK!!!
[root@ZX20150811 /home]# mknod /dev/zx_chrdev_led c 249 23
[root@ZX20150811 /home]# ./app
[ 3950.420000] line:61,chrdev_open is call
[ 3950.420000] line:36,chrdev_write is call
[ 3951.420000] line:36,chrdev_write is call
[ 3952.420000] line:36,chrdev_write is call
[ 3953.420000] line:36,chrdev_write is call
[ 3954.420000] line:36,chrdev_write is call
^C[ 3954.925000] line:68,chrdev_release is call
[root@ZX20150811 /home]#
结果可以知道,换个次设备号应用程序依然能够识别并使用。
还没有评论,来说两句吧...