一、我的第一个linux驱动程序
1.1、采用的linux板子
我采用的是 正点原子 的linux板子,觉得原子的板子还是很不错的。
1.2、硬件原理
由于正点原子使用的设计是通过电平 拉低 是点亮led,拉高则是关闭led。
1.3、个人理解的linux驱动流程
1.构建驱动加载和卸载函数
2.申请设备号,设备号可以指定,或者让系统自动分配
3.初始化创建的cdev结构体。
4.最后自动创建设备节点
1.4、驱动代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
#define NEWCHRLED_NAME "newchrled"
struct newchrled {
struct cdev cdev;
struct class *class;
struct device *device;
dev_t devid;
int major;
int minor;
};
#define CCM_CCGR1 (0X020C406C)
#define SW_MUX_GPIO1_IO03 (0X020E0068)
#define SW_PAD_GPIO1_IO03 (0X020E02F4)
#define GPIO1_DR (0X0209C000)
#define GPIO1_GDIR (0X0209C004)
#define LED_ON 1
#define LED_OFF 0
static void __iomem *ccm_ccgr1;
static void __iomem *sw_mux_gpio01_io03;
static void __iomem *sw_pad_gpio01_io03;
static void __iomem *gpio01_dr;
static void __iomem *gpio01_gdir;
//申请虚拟地址映射
static void led_physical_to_virtual_map(void)
{
ccm_ccgr1 = ioremap(CCM_CCGR1,4);
sw_mux_gpio01_io03 = ioremap(SW_MUX_GPIO1_IO03,4);
sw_pad_gpio01_io03 = ioremap(SW_PAD_GPIO1_IO03,4);
gpio01_dr = ioremap(GPIO1_DR,4);
gpio01_gdir = ioremap(GPIO1_GDIR,4);
}
//释放虚拟地址映射
static void led_physical_to_virtual_unmap(void)
{
iounmap(ccm_ccgr1);
iounmap(sw_mux_gpio01_io03);
iounmap(sw_pad_gpio01_io03);
iounmap(gpio01_dr);
iounmap(gpio01_gdir);
}
static void led_driver_switch(unsigned char sta)
{
unsigned int val = 0;
if(sta == LED_OFF){
//设置默认高点平
val = readl(gpio01_dr);
val |= (1 << 3);
writel(val,gpio01_dr);
}else if(sta == LED_ON){
//设置默认高点平
val = readl(gpio01_dr);
val &= ~(1 << 3);
writel(val,gpio01_dr);
}
}
//注册模块时候初始化GPIO
static void led_driver_init(void)
{
unsigned int val = 0;
//初始化GPIO的时钟
val = readl(ccm_ccgr1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val,ccm_ccgr1);
writel(0x5,sw_mux_gpio01_io03);
writel(0X10B0,sw_pad_gpio01_io03);
//设置为输出
val = readl(gpio01_gdir);
val |= (1 << 3);
writel(val,gpio01_gdir);
//设置默认关闭led
led_driver_switch(LED_OFF);
}
//卸载模块时候禁止led
static void led_driver_freed(void)
{
led_driver_switch(LED_OFF);
}
static int newchrled_open(struct inode *inode, struct file *file)
{
return 0;
}
static int newchrled_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t newchrled_write(struct file *filp, const char __user *buf,
size_t count, loff_t * ppos)
{
int ret = 0;
char read_buf[1];
ret = copy_from_user(read_buf,buf,count);
if(ret < 0){
printk("kernel:write data err\r\n");
}
else{
led_driver_switch(read_buf[0]);
}
return 0;
}
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = newchrled_open,
.write = newchrled_write,
.release = newchrled_release,
};
struct newchrled newchrled;
static int __init newchrled_init(void)
{
int result = 0;
led_physical_to_virtual_map();
led_driver_init();
/*申请设备号*/
newchrled.major = 0; //默认系统自动分配
if (newchrled.major){
newchrled.devid = MKDEV(newchrled.major,0);
result = register_chrdev_region(newchrled.devid,1,NEWCHRLED_NAME);
}
else {
result = alloc_chrdev_region(&newchrled.devid, 0, 1, NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MINOR(newchrled.devid);
}
if(result < 0){
printk("devid err\r\n");
goto devid_err;
}
printk("major:%d minor:%d\r\n",newchrled.major,newchrled.minor);
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev,&newchrled_fops);
result = cdev_add(&newchrled.cdev,newchrled.devid,1);
if(result < 0){
printk("cdev_add err\r\n");
goto cdev_err;
}
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if(IS_ERR(newchrled.class)) {
result = PTR_ERR(newchrled.class);
goto class_create_cdev;
}
newchrled.device = device_create(newchrled.class,NULL,newchrled.devid,NULL,NEWCHRLED_NAME);
if(IS_ERR(newchrled.device)) {
result = PTR_ERR(newchrled.device);
goto device_create_out;
}
printk("newchrled init ok\r\n");
return 0;
device_create_out:
class_destroy(newchrled.class);
class_create_cdev:
cdev_del(&newchrled.cdev);
cdev_err:
unregister_chrdev_region(newchrled.devid,1);
devid_err:
return result;
}
static void __exit newchrled_exit(void)
{
led_driver_freed();
led_physical_to_virtual_unmap();
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
cdev_del(&newchrled.cdev);
unregister_chrdev_region(newchrled.devid,1);
printk("module exit\r\n");
}
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("gale");
1.5、要点个人理解
1、linux使用虚拟内存管理,所以要想实际操作真实的物理地址,必须调用函数申请虚拟地址对应的物理地址,相对应的,在加载函数的时候申请了,那么在卸载驱动的时候必须将这些申请的虚拟地址给释放掉!
2、任何资源在加载时候申请了,除非逼不得已,否则都要在卸载驱动时候将其释放掉。
3、个人采用的是使用**alloc_chrdev_region()**这个函数系统自动申请设备号,最后再将主设备号和从设备号分离。
4、个人使用的是在加载驱动的时候,就自动创建设备节点,否则的话还需要使用这个命令创建设备节点 mknod。
1.6、个人感想
作为我的第一个程序,有些地方可能还需要优化,关于驱动一个IO做出的比较多的步骤,linux应该会有更好的框架来处理这部分,当然这只是我的猜想,关于linux方面的知识还在学习中,后续学到在接着更新。