当前位置: 代码迷 >> 综合 >> 嵌入式内核及驱动开发-04 字符设备驱动编写(readl-writel)
  详细解决方案

嵌入式内核及驱动开发-04 字符设备驱动编写(readl-writel)

热度:98   发布时间:2024-01-20 09:26:28.0

文章目录

  • 驱动和应用程序的设计思想
    • 应用程序和驱动扮演的是什么角色
  • 编写字符设备驱动的步骤和规范
    • 步骤
    • 规范:
  • 操作寄存器地址的方式 readl/writel():
    • 传统的方式
    • 内核提供的方式
  • 例—LED灯闪烁
    • 驱动代码 led_drv.c
    • 应用程序 led_test.c
    • Makefile

驱动和应用程序的设计思想

应用程序和驱动扮演的是什么角色

用户态:应用程序玩策略: 怎么去做1, 一闪一闪2,10s闪一次,也可以1s闪一次3,一直亮4,跑马灯控制权是在应用程序(程序员)
--------------------------------------
内核态:驱动玩机制: 能做什么 led:亮 和 灭

编写字符设备驱动的步骤和规范

步骤

1,实现模块加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);2,在模块加载入口函数中a,申请主设备号  (内核中用于区分和管理不同字符设备)register_chrdev(dev_major, "chr_dev_test", &my_fops);b,创建设备节点文件 (为用户提供一个可操作到文件接口--open())struct  class *class_create(THIS_MODULE, "chr_cls");struct  device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");c, 硬件的初始化1,地址的映射gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);2,中断到申请3,实现硬件的寄存器的初始化// 需要配置gpio功能为输出*gpx2conf &= ~(0xf<<28);*gpx2conf |= (0x1<<28);e,实现file_operationsconst struct file_operations my_fops = {.open = chr_drv_open,.read = chr_drv_read,.write = chr_drv_write,.release = chr_drv_close,};

规范:

1,面向对象编程思想用一个结构体来表示一个对象//设计一个类型,描述一个设备的信息struct led_desc{unsigned int dev_major; //设备号struct class *cls;struct device *dev; //创建设备文件void *reg_virt_base;};struct led_desc *led_dev;//表示一个全局的设备对象// 0(在init中第一步做), 实例化全局的设备对象--分配空间//  GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠)//  #include <linux/slab.h>led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);if(led_dev == NULL){printk(KERN_ERR "malloc error\n");return -ENOMEM;}led_dev->dev_major = 250;2,做出错处理在某个位置出错了,要将之前申请到资源进行释放led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);	led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);if(led_dev->dev_major < 0){printk(KERN_ERR "register_chrdev error\n");ret = -ENODEV;goto err_0;}err_0:kfree(led_dev);return ret;

操作寄存器地址的方式 readl/writel():

传统的方式

volatile unsigned long *gpxcon;
*gpxcon &= ~(0xf<<28);

内核提供的方式

readl/writel();
u32 readl(const volatile void __iomem *addr) 	//从地址中读取地址空间的值void writel(unsigned long value , const volatile void __iomem *addr)		// 将value的值写入到addr地址例子1:
// gpio的输出功能的配置
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28);
writel(value, led_dev->reg_virt_bas);	例子2:
*gpx2dat |= (1<<7);
替换成:
writel( readl(led_dev->reg_virt_base + 4) | (1<<7),   led_dev->reg_virt_base + 4 );

例—LED灯闪烁

驱动代码 led_drv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>#include <uapi/asm-generic/errno-base.h>//设计一个类型,描述一个设备的信息
typedef struct _led_desc
{unsigned int dev_major;//主设备号struct class *cls;struct device* dev;	  //创建设备文件void *reg_virte_base; //存放虚拟地址的首地址(寄存器地址的基准值)
}LED_DESC_T;LED_DESC_T *led_dev;    //申明设备对象//物理地址
#define GPX2_CON 0x11000C40  
#define GPX2_SIZE 8static int kernel_val = 555;ssize_t led_drv_read(struct file * filep, char __user * buf, size_t count, loff_t *fops)
{int ret;printk("---------%s-------------\n",__FUNCTION__);//从内核空间拷贝数据到用户空间ret = copy_to_user(buf,&kernel_val,count);if(ret > 0){printk("copy_to_user error\n");return -EFAULT;}return 0;
}ssize_t led_drv_write(struct file *filep, const char __user * buf, size_t count, loff_t *fops)
{int ret;int value;printk("---------%s-------------\n",__FUNCTION__);//从用户空间拷贝数据到内核空间ret = copy_from_user(&value,buf, count);if(ret > 0){printk("copy_to_user error\n");return -EFAULT;}//控制GPX2_7 I/O口电平变化if(value){writel( readl(led_dev->reg_virte_base + 4) | (1<<7),   led_dev->reg_virte_base  + 4 );}else{writel( readl(led_dev->reg_virte_base + 4) & ~(1<<7),   led_dev->reg_virte_base + 4 );}return 0;}int led_drv_open(struct inode *inode, struct file *filep)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}
int led_drv_close(struct inode *inode, struct file *filep)
{printk("---------%s-------------\n",__FUNCTION__);return 0;
}const struct file_operations my_fops = {.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_close,
};static int __init led_dev_init(void)
{int ret;printk("---------%s-------------\n",__FUNCTION__);//0,  实例化全局的设备对象---分配空间led_dev = kmalloc(sizeof( LED_DESC_T), GFP_KERNEL);if( NULL == led_dev){ printk(KERN_ERR "malloc error\n");return -ENOMEM;}//1,  一般都是申请设备号资源//申请主设备号#if 0	//静态申请主设备号led_dev->dev_major = 250;ret = register_chrdev(led_dev->dev_major, "led_dev_test", &my_fops);if(ret == 0){printk("register ok\n");}else{printk("register failed\n");ret = -EINVAL;goto err_0;}
#else 	//动态态申请主设备号led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);if(led_dev->dev_major < 0){printk(KERN_ERR "register_chrdev error\n");ret = -ENODEV;goto err_0;}else{printk("register ok\n");}#endif//2,创建设备文件led_dev->cls = class_create(THIS_MODULE, "led_cls");if(IS_ERR(led_dev->cls)){printk(KERN_ERR "class_create error\n");ret = PTR_ERR(led_dev->cls); 	//将指针出错的具体原因转换成一个出错码goto err_1;}led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major, 0),NULL,"led%d",0); 	// 	/dev/led0if(IS_ERR(led_dev->dev)){printk(KERN_ERR "device_create error\n");ret = PTR_ERR(led_dev->dev); 	//将指针出错的具体原因转换成一个出错码goto err_2;}// 3, 硬件初始化// 对地址进行映射led_dev->reg_virte_base = ioremap(GPX2_CON, GPX2_SIZE);if(led_dev->reg_virte_base == NULL){printk(KERN_ERR "ioremap error\n");ret = -ENOMEM;goto err_3;}//配置GPIO的功能为输出#if 0 u32 value = readl(led_dev->reg_virte_base);value = (value & ~(0xf << 28)) | (0x1 << 28);writel(value, led_dev->reg_virte_base);
#else writel(((readl(led_dev->reg_virte_base) & ~(0xf << 28)) | (0x1 << 28)), led_dev->reg_virte_base);
#endifreturn 0;err_3:device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, 0));err_2:class_destroy(led_dev->cls);err_1:unregister_chrdev(led_dev->dev_major,"led_dev_test");err_0:kfree(led_dev);	return ret;}static void __exit led_dev_exit(void)
{printk("---------%s-------------\n",__FUNCTION__);//卸载一般都是释放资源//取消地址映射iounmap(led_dev->reg_virte_base);//销毁节点和类device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));class_destroy(led_dev->cls);//释放设备号unregister_chrdev(led_dev->dev_major,"led_dev_test");//释放动态内存kfree(led_dev);}module_init(led_dev_init);
module_exit(led_dev_exit);MODULE_LICENSE("GPL");

应用程序 led_test.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc, char *argv[])
{//调用驱动int fd;int value = 0;fd = open("/dev/led0", O_RDWR);if (fd < 0){perror("led_test open");exit(1);}read(fd, &value, 4);printf("__USER__ : value = %d\n", value);while (1){value = 1;write(fd, &value, 4);sleep(1);value = 0;write(fd, &value, 4);sleep(1);}close(fd);return 0;
}

Makefile

ROOTFS_DIR = /nfs/rootfs/MODULE_NAME  = led_drv
APP_NAME = led_test
CORSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CORSS_COMPILE)gccifeq ($(KERNELRELEASE),)KERNEL_DIR = /home/zzw/share/linux-3.14.79 	
CPU_DIR = $(shell pwd) all:make -C $(KERNEL_DIR) M=$(CPU_DIR) modules$(CC) $(APP_NAME).c -o $(APP_NAME)clean:make -C $(KERNEL_DIR) M=$(CPU_DIR) cleanrm -rf  $(APP_NAME)install:sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_moduleelseobj-m += $(MODULE_NAME).o endif