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