您的位置:首页 > 其它

字符设备驱动----LED驱动程序

2015-11-15 11:42 267 查看

一. 概念介绍

一般用户在应用程序里调用的 open, read, write 函数是 c 库的函数,

这些函数会触发 swi val异常,从而引发系统调用,进入到内核空间,

内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。



例如:我们有一个函数,

int main()
{
int fd1, fd2;
int val = 1;

fd1 = open("/dev/led", O_RDWR);
write(fd1, &val, 4);

fd2 = open("hello.txt", O_RDWR);
write(fd2, &val, 4);
}


函数里相同的open、write函数,引发的不同的行为,一个是操控硬件,一个是写文件。

简单的调用关系如下:

用户 –> 系统调用 –> 驱动程序

open –> sys.open –> led.open

write –> sys.write –> led.write

二. 字符设备驱动框架

实现步骤:

1. 实现驱动的 led.open, led.write, led.read 操作

2. 定义file_operations结构体, 把驱动函数填充到里面

3. 把这个结构告诉内核, 通个函数 register_chrdev(major, “first_drv”, &first_drv_fops) 来实现

4. 谁来调用注册函数 –>驱动的入口函数来调用这个注册函数, first_drv_init

5. 修饰一下这个函数入口函数,module_init(first_drv_init)

//第一步:驱动功能实现
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
return 0;
}

static ssize_t first_drv_write(struct file *file,
const char __user *buf,
size_t count,
loff_t * ppos)
{
//printk("first_drv_write\n");
return 0;
}

//第二步:定义结构体,并把驱动函数填充进去
static struct file_operations first_drv_fops = {
.owner  =   THIS_MODULE,
.open =   first_drv_open,
.write =    first_drv_write,
};

//第四步:实现驱动入口函数
int major;
static int first_drv_init(void)
{
//第三步:把结构体告诉内核
major = register_chrdev(0, "first_drv", &first_drv_fops);// 注册告诉内核

return 0;
}

static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
}

//第五步:修饰入口函数,及退出函数
module_init(first_drv_init);
module_exit(first_drv_exit);


三. 关联 [设备号] 与 [设备节点]

设备号要与设备结点关联起来,才能通过open(“/dev/xyz”)方便的操作。

1. 设置主设备号

驱动程序可以自动分配主设备号, 也可以手工指定

// 设置为 0 时是系统自动分配主设备号
major = register_chrdev(0, "first_drv", &first_drv_fops);
// 通过 [cat /proc/device] 看一下系统为我们的first_drv分配的设备号是多少

// 手动分配 111主设备号给 first_drv
register_chrdev(111, "first_drv", &first_drv_fops);


2. 设置设备节点

当应该程序 执行 open(“/dev/xyz”) 操作时,这个/dev/xyz怎么来的

2.1 手动创建

// 创建设备节点
mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号

//查看设备信息:
ls -l /dev/xyz


2.2 自动创建

mdev – 根据系统信息创建设备节点

int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops);

//创建设备信息,执行后会出现 /sys/class/firstdrv
firstdrv_class = class_create(THIS_MODULE, "firstdrv");

//创建设备节点,就是根据上面的设备信息来的
firstdrv_class_dev = class_device_create(firstdrv_class,
NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */

return 0;
}

static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv");

//删除节点及信息
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}


四. 完善LED驱动

完善LED驱动,也就是硬件的操作。

单片机中可以直接操作物理地址,但在驱动里只能操作虚拟地址

虚拟地址怎么来的,用 ioremap( ) 函数来映射,映射完后操作虚拟地址就像操作物理地址一样。

static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops);
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class,
NULL, MKDEV(major, 0), NULL, "xyz");

//映射 GPIO 的物理地址 0x56000050 到虚拟地址, gpfcon操作的是虚拟地址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;

return 0;
}


映射完后,就可以操作这些地址,来控制硬件寄存器

static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配制GPF4为输出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}


另外,当用户空间的数据要传到内核空间里,同样不能直接用,

也要通过 copy_from_user() 函数,把用户空间的值传到内核空间里。

static ssize_t first_drv_write(struct file *file,
const char __user *buf,
size_t count, loff_t * ppos)
{
int val;

//printk("first_drv_write\n");

//把用户空间的值 copy 给内核空间
copy_from_user(&val, buf, count); //    copy_to_user();

if (val == 1)
{
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内核