Linux内核驱动并发控制

一 动态注册设备号


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内核源码树的路径
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值