Linux内核驱动并发控制

时间:2021-11-20 03:47:58
一 动态注册设备号


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 251

MODULE_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内核源码树的路径