-------- 创建普通字符设备驱动模型 ---------
①定义一个字符设备------>struct cdev
②定义并初始化一个文件操作集------>struct file_operations
③为字符设备申请设备号
③初始化字符设备------>cdev_init(struct cdev*, struct file_operations*);
④把字符设备加入Linux内核------>add_cdev(struct cdev*, 设备号, 次设备的数量);
-------- 自动创建设备文件 ---------
⑥创建class------>class_create(THIS_MODULE, 类名);
⑦创建device
-------- 得到虚拟地址,通过虚拟地址访问寄存器 ------
⑧申请物理内存区------>request_mem_region(内存区起始物理地址, 内存区大小, 内存区名);
⑨IO内存动态映射------>ioremap(映射内存区起始物理地址, 映射内存区大小);
//led_drv.c(GEC6818开发板-->led灯驱动例子)
//测试程序:test.c (在最下面)
//头文件来源于linux内核源码
#include /* Needed for KERN_INFO */
#include /* Needed by all modules */
#include /*Needed for cdev*/
#include /*Needed for file_operations*/
#include /*Needed for create device file*/
#include /*Needed for error node*/
#include /*Needed for copy from user or copy to user*/
#include /*Needed for ioremap*/
//1.定义一个字符设备
static struct cdev led_cdev;
//2.给字符设备申请设备号(在gec6818_led_init()中申请)
static unsigned int led_major; //主设备号
static unsigned int led_minor; //次设备号
static dev_t led_dev_num; //设备号
/*(设备号的前12位是主设备号,后20位是次设备号)*/
//定义类、设备、内存资源指针
static struct class *led_class;
static struct device *led_device;
static struct resource *led_res;
//定义寄存器指针
static void __iomem *GPIOC_BASE;//让编译器compiler忽略对本指针的检测
static void __iomem *GPIOCOUT;
static void __iomem *GPIOCOUTENB;
static void __iomem *GPIOCALTFN0;
static void __iomem *GPIOCALTFN1;
static void __iomem *GPIOE_BASE;
static void __iomem *GPIOEOUT;
static void __iomem *GPIOEOUTENB;
static void __iomem *GPIOEALTFN0;
//定义IOCTL命令-->使用方法:ioctl(int fd, unsigned int cmd, unsigned int led_number);
#define GEC6818_LED_MAGIC 'L'
#define GEC6818_LED_ON _IOW(GEC6818_LED_MAGIC, 1, unsigned int)
#define GEC6818_LED_OFF _IOW(GEC6818_LED_MAGIC, 2, unsigned int)
static int gec6818_led_open(struct inode *inode, struct file *filp)
{
printk("led driver is openning\n");
return 0;
}
static int gec6818_led_release(struct inode *inode, struct file *filp)
{
*(unsigned int *)GPIOCOUT |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
*(unsigned int *)GPIOEOUT |= (1<<13);
printk("led driver is closing\n");
return 0;
}
static long gec6818_led_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if(_IOC_TYPE(cmd) != GEC6818_LED_MAGIC) return -EINVAL;
if(_IOC_NR(cmd) != 2 && _IOC_NR(cmd) != 1) return -ENOIOCTLCMD;
switch(cmd)
{
case GEC6818_LED_OFF:
switch(arg)
{
case 7 : *(unsigned int *)GPIOEOUT |= (1<<13);break;
case 8 : *(unsigned int *)GPIOCOUT |= (1<<17);break;
case 9 : *(unsigned int *)GPIOCOUT |= (1<<8); break;
case 10: *(unsigned int *)GPIOCOUT |= (1<<7); break;
case 11: *(unsigned int *)GPIOCOUT |= (1<<12);break;
default : return -EINVAL;
}
break;
case GEC6818_LED_ON:
switch(arg)
{
case 7 : *(unsigned int *)GPIOEOUT &= ~(1<<13); break;
case 8 : *(unsigned int *)GPIOCOUT &= ~(1<<17); break;
case 9 : *(unsigned int *)GPIOCOUT &= ~(1<<8); break;
case 10: *(unsigned int *)GPIOCOUT &= ~(1<<7); break;
case 11: *(unsigned int *)GPIOCOUT &= ~(1<<12); break;
default : return -EINVAL;
}
}
return 0;
}
//3.定义一个文件操作集并初始化
static const struct file_operations gec6818_led_fops = {
.owner = THIS_MODULE,
// .write = gec6818_led_write,
.unlocked_ioctl = gec6818_led_unlocked_ioctl,
.open = gec6818_led_open,
.release = gec6818_led_release,
};
//驱动的安装函数
static int __init gec6818_led_init(void)
{
//2.申请设备号(如果主设备号设置为0 则由系统动态分配)
int ret;
led_major = 0;
led_minor = 0;
if (led_major == 0)
{
ret = alloc_chrdev_region(&led_dev_num, led_minor, 1, "led_driver");
}
else
{
led_dev_num = MKDEV(led_major, led_minor); //把主次设备号转换成设备号
ret = register_chrdev_region(led_dev_num, 1, "led_driver");
}
if (ret < 0) //出错时,跳转到出错处理
{
printk("can not get led device number\n");
goto err_dev_num;
}
//4.初始化字符设备cdev(把文件操作集与字符设备关联起来)
cdev_init(&led_cdev, &gec6818_led_fops);
//5.把初始化好的字符设备cdev加入内核
ret = cdev_add(&led_cdev, led_dev_num, 1);
if (ret < 0)
{
printk("cdev add error\n");
goto err_cdev_add;//出错时,跳转到出错处理
}
//6.驱动程序自动创建设备文件(操作系统必须有mdev或udev)
//6.1 创建class
led_class = class_create(THIS_MODULE, "led_class");
if (IS_ERR(led_class)) //判断指针是否有效
{
ret = PTR_ERR(led_class); //用指针求得错误码,并赋值给ret
printk("led driver class create error\n");
goto err_create_class;
}
//6.2 创建device
led_device = device_create(led_class, NULL, led_dev_num, NULL, "led_drv");
if (IS_ERR(led_device)) //判断指针是否有效
{
ret = PTR_ERR(led_device);//用指针求得错误码,并赋值给ret
printk("led driver device create error\n");
goto err_create_device;
}
//7.申请物理内存区(用于实现内存资源的互斥访问,不申请亦可)
led_res = request_mem_region(0xC001C000, 0x1000, "GPIOC_MEM");
if (led_res == NULL)
{
printk("request memory error\n");
ret = -EBUSY;
goto err_request_mem;
}
/************************************************************************************/
//8.IO内存的动态映射
GPIOC_BASE = ioremap(0xC001C000, 0x1000);
if (GPIOC_BASE == NULL)
{
printk("ioremap GPIOC_BASE error\n");
ret = -EFAULT;
goto err_ioremap_C;
}
GPIOE_BASE = ioremap(0xC001E000, 0x1000);
if (GPIOE_BASE == NULL)
{
printk("ioremap GPIOE_BASE error\n");
ret = -EFAULT;
goto err_ioremap_E;
}
//通过映射的基地址计算各寄存器的地址
GPIOCOUT = GPIOC_BASE + 0X00;
GPIOCOUTENB = GPIOC_BASE + 0X04;
GPIOCALTFN0 = GPIOC_BASE + 0X20;
GPIOCALTFN1 = GPIOC_BASE + 0X24;
GPIOEOUT = GPIOE_BASE + 0X00;
GPIOEOUTENB = GPIOE_BASE + 0X04;
GPIOEALTFN0 = GPIOE_BASE + 0X20;
//LED的初始化并关闭全部LED
*(unsigned int *)GPIOCOUTENB |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
*(unsigned int *)GPIOCALTFN0 &= ~((3<<14) | (3<<16) | (3<<24));
*(unsigned int *)GPIOCALTFN0 |= ((1<<14) | (1<<16) | (1<<24));
*(unsigned int *)GPIOCALTFN1 &= ~(3<<2);
*(unsigned int *)GPIOCALTFN1 |= (1<<2);
*(unsigned int *)GPIOCOUT |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
*(unsigned int *)GPIOEOUTENB |= (1<<13);
*(unsigned int *)GPIOEALTFN0 &= ~(3<<26);
*(unsigned int *)GPIOEOUT |= (1<<13);
//驱动安装完成,并输出相关信息
printk(KERN_INFO "gec6818_led_init\n");
printk(KERN_INFO "device_name : led_drv\n");
printk(KERN_INFO "led_major : %d\n", MAJOR(led_dev_num));
printk(KERN_INFO "led_minor : %d\n", MINOR(led_dev_num));
// printk("gec6818_led_init\n"); //缺省优先级的printk将使用系统默认优先级
return 0;
//出错处理
err_ioremap_E:
iounmap(GPIOC_BASE);
err_ioremap_C:
release_mem_region(0xC001C000, 0x1000);//释放已经申请的物理内存区
err_request_mem:
device_destroy(led_class, led_dev_num);//删除已经创建的设备文件
err_create_device:
class_destroy(led_class);//删除已经创建的类led_class
err_create_class:
cdev_del(&led_cdev);//从内核删除已经成功添加的字符设备
err_cdev_add:
unregister_chrdev_region(led_dev_num, 1); //释放已经申请的设备号
err_dev_num:
return ret;//出错时返回错误码
}
//驱动的卸载函数
static void __exit gec6818_led_exit(void)
{
iounmap(GPIOE_BASE);
iounmap(GPIOC_BASE);//解除内存地址映射
release_mem_region(0xC001C000, 0x1000);
device_destroy(led_class, led_dev_num);
class_destroy(led_class);
cdev_del(&led_cdev);
unregister_chrdev_region(led_dev_num, 1);
printk(KERN_INFO "gec6818_led_exit\n");
}
//module的入口和出口函数
//#insmod led_drv.ko --->module_init()--->驱动程序的安装函数gec6818_led_init()
module_init(gec6818_led_init);//入口函数
//#rmmod led_drv.ko --->module_exit()--->驱动程序的卸载函数gec6818_led_exit()
module_exit(gec6818_led_exit);//出口函数
//module的描述,不是必需的。#modinfo led_drv.ko
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("led driver for GEC6818");
MODULE_LICENSE("GPL"); //符合GPL协议
MODULE_VERSION("V1.0");
①定义字符设备管理结构体变量(具体的结构体定义在内核头文件)
②定义并初始化一个文件操作集(用于驱动与应用程序的交互)
1)文件操作集结构体定义在内核头文件
2)选择有需要的接口函数进行初始化
3)定义文件操作集中初始化的接口函数
③为字符设备申请设备号(包括主设备号、次设备号和设备号)
※ 申请设备号的两种方法:动态申请(由系统自动分配)和 手动指定设备号。
④初始化字符设备(把cdev与文件操作集关联起来)
⑤把初始化好的字符设备加入Linux内核
⑥创建设备文件 \ 设备节点
1)手动创建设备文件节点
~:mknod 设备类型 设备文件名 主设备号 次设备号
例:[ root@GEC6818 / ]#mknod /dev/led_drv c 100 0
※ 可以使用#cat /proc/devices 查看创建是否成功
2)自动创建设备文件节点(系统中必须有medv或udev工具)
mdev是udev的简化版,这个工具可以根据驱动的class和device来创建设备文件节点。
⑦申请物理内存区(类似于互斥锁功能)
⑧IO内存动态映射(把物理地址映射到操作系统可操作的虚拟地址)
⑨按照实际要求配置寄存器的值
【具体参考:led_drv.c】
⑩出错处理(用goto语句实现)
每一部都设置出错处理,出错时释放已经申请的资源。
【做到这里,驱动安装步骤结束】
※ 如果MODULE_LICENSE 不写,内核会报警告【信息:污染内核】。
//test.c --> led_drv.ko 适用的 led灯测试程序
#include
#include
#include
#include
#include
#include
#include
#define GEC6818_LED_MAGIC 'L'
#define GEC6818_LED_ON _IOW(GEC6818_LED_MAGIC, 1, unsigned int)
#define GEC6818_LED_OFF _IOW(GEC6818_LED_MAGIC, 2, unsigned int)
int main(void)
{
int fd;
long ret;
unsigned int num = 7;
fd = open("/dev/led_drv", O_WRONLY);
if(fd < 0)
{
perror("open led driver");
return -1;
}
while(1)
{
ret = ioctl(fd, GEC6818_LED_ON, num);
sleep(1);
printf("num = %d\n", num);
num += 1;
if (num > 11)
{
num = 7;
break;
}
}
while(1)
{
ret = ioctl(fd, GEC6818_LED_OFF, num);
sleep(1);
printf("num down = %d\n", num);
num += 1;
if (num > 11)
{
break;
}
}
close(fd);
return 0;
}