Linux内核驱动并发控制

来源:互联网 发布:中仕网络 编辑:程序博客网 时间:2024/05/22 17:45
一 动态注册设备号


int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
参数:
@dev         获得动态设备号 
@baseminor   第一个次设备号
@count       此设备号的个数
@name        名字,可以在/proc/devices文件看到
返回值:
成功返回0,失败返回负的错误码


二、通过主设备号和次设备号生成设备号
宏:MKDEV(major,minor)


三、通过设备号获得主设备号或次设备号


获得主设备号宏:MAJOR(dev_num)
获得次设备号宏:MINOR(dev_num)




四 如何添加驱动的时候,自动生成设备节点


1.创建类(在/sys/class目录下创建一个子目录)


struct class *class_create(struct module *owner, char *name)
参数 :
@owner  THIS_MODULE 
@name   子目录的名字
放回值:
成功返回有效指针,失败返回负的错误码




2.在sysfs文件中注册设备(导出一些参数:主设备号和次设备号)
struct device *device_create(struct class *class, struct device *parent,
    dev_t devt, void *drvdata, const char *fmt, ...)
参数:
@class   类结构体首地址
@parent  NULL 
@devt    设备号 
@drvdata NULL 
@fmt     格式化串 "mycdev" 或 "mycdev%d",1
成功返回有效指针,失败返回负的错误码


3.如何判断是有效指针还是负的错误码


宏    :IS_ERR(指针)
返回值:
指针是负的错误码,返回真,否则返回假


宏   :PRT_ERR(指针)
返回值:
负的错误码




五、用户空间与驱动程序数据交互


long copy_to_user(void __user *to,const void *from, unsigned long n)
功能:将内核空间的数据拷贝到用户空间
参数:
@__user to :用户空间的地址 
@from      :内核空间的地址 
@n         :大小  


返回值:
成功返回0,失败返回未拷贝的数据大小


long copy_from_user(void *to,const void __user * from, unsigned long n)
功能:将用户空间的数据拷贝给内核空间
参数:
@to         :内核空间的地址
@__user from:用户空间的地址 
@n          :大小
成功返回0,失败返回未拷贝的数据大小


2.单个数据拷贝


宏  :put_user(value, ptr)
功能:将value写到ptr指向的地址(用户)


宏  :get_user(value, ptr)
功能:将ptr指向的地址(用户)内容读到value




六  通过命令来控制设备


1.设计命令


#define CMD_TYPE   'k'
#define CMD_CDEV_CLEAN    _IO(CMD_TYPE,0x10)
#define CMD_SET_CDEV_LEN  _IOW(CMD_TYPE,0x11,int)
#define CMD_GET_CDEV_LEN  _IOR(CMD_TYPE,0x12,int)


2.驱动ioctl函数
int  (*ioctl) (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);[<=2.6内核之前]
long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);       [> 2.6内核    ]


参数:
@cmd  传递的命令
@arg  传递的参数[数值or地址]


如过arg是一个地址值,如何使用?


这个地址是用户空间的,put_user/get_user来操作




七  LED驱动编写


GPG3CON   Address = 0xE030_01C0
GPG3DAT   Address = 0xE030_01C4


手册上查询的地址是实际物理地址,操作系统使用虚拟地址,所以我们需要将物理地址做个映射.


#include <asm/io.h>


void * ioremap(unsigned long phy_addr,int size)
功能:将物理地址与虚拟地址映射
参数:
@phy_addr 物理地址  
@size     映射大小




static inline u32  readl(const volatile void __iomem *addr)
{
return *(const volatile u32 __force *) addr;
}
功能:从一个地址中读取四个字节




static inline void  writel(u32 b, volatile void __iomem *addr)
{
*(volatile u32 __force *) addr = b;
}
 
功能:向一个地址中写四个字节




例如:将GPG3CON寄存器设置IO工作模式为输出模式


int reg;
void *con = ioremap(0xE03001C0,4);




//读值
reg = real(con);
//修改
reg &= ~(0xffff << 0);
reg |=  (0x1111 << 0);
//写值
writel(reg,con);


-----------------------------------------------------------------------------------------
1.
struct led_devie
{
void *con;//保存映射后的地址
void *data;//保存映射后的地址
struct cdev cdev;
struct class *cls;
struct device *device;
};


2.实现 file_opertaions 


xxx_open,xxx_ioctl,xxx_release 


3.模块入口函数干的事情
 [1]kmalloc/kzalloc分配空间
 [2]初始化字符设备
 [3]申请设备号
 [4]添加字符设备到系统
 [5]创建一个class
 [6]创建一个device 
 [7]映射GPG3CON和GPG3DAT地址,映射后保存在con和data
 
4.xxx_open干的事情
  [1]设置GPIO为输出模式
  [2]关闭所有的LED灯(清GPG3DAT)
   
5.xxx_ioctl干的事情
CMD_LED_ON :打开所有灯
CMD_LED_OFF:关闭所有灯

6.模块出口函数

释放分配的资源(内存,映射地址,设备号....)

#include <linux/init.h>#include <linux/module.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/device.h>#include <linux/err.h>#include <asm/uaccess.h>#include <linux/slab.h>#include <asm-generic/ioctl.h>#include <asm/io.h>#include "head.h"#define LED_REG_BASE  0xE03001C0#define LED_CON       0 #define LED_DAT       4#define LED_MEM_SIZE  8#define DRIVER_MAJOR  251MODULE_LICENSE("GPL");struct led_device{int major;void *reg;struct class *cls;struct device *dev;struct cdev cdev;};struct led_device *pled;int led_device_open(struct inode *inode, struct file *file){int val;printk("test_device_open\n");val  = readl(pled->reg + LED_CON);val &= ~(0xffff << 0);val |=  (0x1111 << 0);writel(val, pled->reg + LED_CON);return 0;}long led_device_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg){int val;val = readl(pled->reg + LED_DAT);switch(cmd){case LED_DEVICE_ON:val |= 0xf;break;case LED_DEVICE_OF:val &= ~0xf;break;default:return -ENOTTY;}writel(val, pled->reg + LED_DAT);return 0;}int led_device_release(struct inode *inode, struct file *file){int val;printk("test_device_release\n");val = readl(pled->reg + LED_DAT);val &= ~0xf;writel(val, pled->reg + LED_DAT);return 0;}struct file_operations led_fops ={.owner   = THIS_MODULE,.open    = led_device_open,.release = led_device_release,.unlocked_ioctl = led_device_unlocked_ioctl,};int led_device_init(void){int ret;dev_t dev_num;pled = kzalloc(sizeof(struct led_device),GFP_KERNEL);if(!pled){printk("Fail to kzalloc");ret = -ENOMEM;goto err_kzalloc;}cdev_init(&pled->cdev, &led_fops);dev_num = MKDEV(DRIVER_MAJOR,0);ret = register_chrdev_region(dev_num,1,"led_device");if(ret < 0){//动态注册设备号ret = alloc_chrdev_region(&dev_num,0,1,"led_device");if(ret < 0){printk("Fail to alloc_chrdev_region\n");goto err_alloc_register_chrdev_region;}}pled->major = MAJOR(dev_num);ret = cdev_add(&pled->cdev, dev_num,1);if(ret < 0){printk("Fail to cdev_add\n");goto err_cdev_add;}//自动创建设备节点pled->cls = class_create(THIS_MODULE,"led");if(IS_ERR(pled->cls)){printk("Fail to class create");ret = PTR_ERR(pled->cls);goto err_class_create;}pled->dev = device_create(pled->cls,NULL,dev_num,NULL, "led_device");if(IS_ERR(pled->dev)){printk("Fail to device_create\n");ret = PTR_ERR(pled->dev);goto err_device_create;}//映射寄存器地址空间pled->reg = ioremap(LED_REG_BASE,LED_MEM_SIZE);if(!pled->reg){printk("Fail to ioremap\n");ret = -EINVAL;goto err_ioremap;}return 0;err_ioremap:device_destroy(pled->cls, dev_num);err_device_create:class_destroy(pled->cls);err_class_create:cdev_del(&pled->cdev);err_cdev_add:unregister_chrdev_region(dev_num,1);err_alloc_register_chrdev_region:kfree(pled);err_kzalloc:return ret;}void led_device_exit(void){dev_t dev_num;dev_num = MKDEV(pled->major,0);device_destroy(pled->cls, dev_num);class_destroy(pled->cls);cdev_del(&pled->cdev);unregister_chrdev_region(dev_num,1);kfree(pled);return;}module_init(led_device_init);module_exit(led_device_exit);



注意:
此时KERNEL_DIR是开发板Linux内核源码树的路径
0 0
原创粉丝点击