对Linux来说,设备驱动也是文件。驱动控制硬件的过程,实际上是对驱动文件的读写操作。
对内核来说,如何获取唯一的文件标识呢?当然是通过file结构体中的,inode结构体识别应用层打开的到底是哪一个设备文件。
实验操作及要求:
在串口工具进行输入:
echo 1 > /dev/myled0 ---->led1灯点亮
echo 0 > /dev/myled0 ---->led1灯熄灭
echo 1 > /dev/myled1 ---->led1灯点亮
echo 0 > /dev/myled1 ---->led1灯熄灭
echo 1 > /dev/myled2 ---->led1灯点亮
echo 0 > /dev/myled2 ---->led1灯熄灭
linux@ubuntu:/sys/class/myled$ ls /dev/myled* -ll
crw------- 1 root root 236, 0 Nov 18 14:55 /dev/myled0 ----->控制PE10(LED1)
crw------- 1 root root 236, 1 Nov 18 14:55 /dev/myled1----->控制PF10(LED2)
crw------- 1 root root 236, 2 Nov 18 14:55 /dev/myled2----->控制PE8(LED3)
2.驱动:
open:在open函数中获取到次设备号,用私有数据传参,传递给write函数
write:在write函数,判断次设备号,就知道操作的是哪盏灯
3.要求:
1)分部实现注册字符设备驱动
2)自动创建设备节点
3)通过结构体对led灯地址进行映射
4)次设备号完成私有数据传参
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "led.h"
#define CNAME "myled"
unsigned int major=0;
int minor=0;
struct class *cls;
//指向字符设备号
struct device *dev;
const int count=3;
//指向字符设备驱动结构体
struct cdev *cdev;
volatile unsigned int* virt_rcc;
volatile gpio_t* virt_gpioe;
volatile gpio_t* virt_gpiof;
int mycdev_open(struct inode *inode,struct file *file)
{
//取出次设备号
int mino;
mino=MINOR(inode->i_rdev);
//将次设备号放到file结构体私有数据中
file->private_data = (void*)mino;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t mycdev_read(struct file*file,char __user *ubuf,size_t size,loff_t *loffs)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
//ssize_t mycdev_write(struct file *file,char __user *ubuf,size_t size,loff_t *loff)错误写法
ssize_t mycdev_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff)
{
//私有数据传参
//读取用户空间数据到内核(传输数据以内核空间大小为准)
char kbuf[128]="0";
int ret=0;
if(size>sizeof(kbuf)) size= sizeof(kbuf);
ret=copy_from_user(kbuf,ubuf,size);
if(ret)
{
printk("copy_from_user is failed\n");
return -EIO;
}
//取出次设备号
minor = (int)file->private_data;
switch(minor)
{
case LED1:
if(kbuf[0]=='0')
{
virt_gpioe->ODR &=(~(0x1<<10));
printk("LED1 on\n");
}else
{
virt_gpioe->ODR |=(0x1<<10);
printk("LED1 off\n");
}
break;
case LED2:
if(kbuf[0]=='0')
{
virt_gpiof->ODR &=(~(0x1<<10));
printk("LED2 on\n");
}else{
virt_gpiof->ODR |=(0x1<<10);
printk("LED2 off\n");
}
break;
case LED3:
if(kbuf[0]=='0')
{
virt_gpioe->ODR &=(~(0x1<<8));
printk("LED3 on\n");
}
else
virt_gpioe->ODR |=(0x1<<8);
break;
}
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
int mycdev_close(struct inode* inode,struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops={
.open=mycdev_open,
.read=mycdev_read,
.write=mycdev_write,
.release=mycdev_close,
};
static int __init mycdev_init(void)
{
int ret;
dev_t devno;
int i;
//1.分步注册字符设备驱动
//1)分配dev结构体
cdev=cdev_alloc();
if(NULL==cdev)
{
printk("cdev alloc is failed\n");
return -EIO;
}
//2)初始化cdev结构体
cdev_init(cdev,&fops);
//3)动态申请设备号,成功返回0,失败返回错误码
ret=alloc_chrdev_region(&devno,0,count,CNAME);
if(ret)
{
printk("alloc_chrdev_region is failed\n");
return -ENOMEM;
}
//成功申请到设备号,调函数获取主次设备号(12+20)
major=MAJOR(devno);
minor=MINOR(devno);
//3)驱动注册
ret=cdev_add(cdev,MKDEV(major,minor),count);
if(ret)
{
printk("dev add is failed\n");
return -EIO;
}
//2.自动创建设备节点
//1)向上层提交目录信息
cls=class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls))
{
return PTR_ERR(cls);
}
//2.向上层提交设备节点
for(i=0;i<count;i++)
{
dev=device_create(cls,NULL,MKDEV(major,i),NULL,"myled%d",i);
if(IS_ERR(dev))
{
return PTR_ERR(dev);
}
}
//3.通过结构体对LED灯进行映射
//将GPIOE和GPIOF物理地址映射为虚拟地址
//1)将rcc地址进行映射
virt_rcc=ioremap(PHY_RCC,4);
if(NULL==virt_rcc)
{
printk("rcc ioremap failed\n");
return -ENOMEM;
}
//2)将gpioe地址进行映射
virt_gpioe=ioremap(PHY_GPIOE,sizeof(gpio_t));
if(NULL==virt_gpioe)
{
printk("virt_gpioe ioremap failed\n");
return -ENOMEM;
}
//2)将gpiof地址进行映射
virt_gpiof=ioremap(PHY_GPIOF,sizeof(gpio_t));
if(NULL==virt_gpiof)
{
printk("virt_gpiof ioremap failed\n");
return -ENOMEM;
}
//灯的初始化
//LED1--PE10
*virt_rcc |= (0x1<<4);
virt_gpioe->MODER &= (~(0x3<<20));
virt_gpioe->MODER |= (0x1<<20);
//设置默认输出低电平
virt_gpioe->ODR |= (~(0x1<<10));
//LED2--PF10
*virt_rcc |= (0x1<<5);
virt_gpiof->MODER &= (~(0x3<<20));
virt_gpiof->MODER |= (0x1<<20);
virt_gpiof->ODR &= (~(0x1<<10));
//LED3--PE8
virt_gpioe->MODER &= (~(0x3<<16));
virt_gpioe->MODER |= (0x1<<16);
virt_gpioe->ODR &= (~(0x1<<8));
return 0;
}
static void __exit mycdev_exit(void)
{
int i;
iounmap(virt_rcc);
iounmap(virt_gpioe);
iounmap(virt_gpiof);
//销毁节点
for(i=0;i<count;i++)
{
device_destroy(cls,MKDEV(major,i));
}
//销毁目录信息
class_destroy(cls);
//驱动注销
cdev_del(cdev);
//注销设备号
unregister_chrdev_region(MKDEV(major,minor),count);
//5.释放dev结构体
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
测试:
1.查看到3个设备节点