Linux驱动开发(二)

文章详细介绍了Linux内核中如何注册和操作字符设备驱动,包括手动和自动分配设备号,使用cdev结构体,以及内核与驱动间的数据交互。示例代码展示了两种注册字符设备的方法,一种使用register_chrdev,另一种使用alloc_chrdev_region,同时涉及内存映射和设备文件的操作函数。
摘要由CSDN通过智能技术生成

一、驱动流程

驱动需要以下几个步骤才能完成对硬件的访问和操作:

  1. 模块加载函数 module_init
  2. 注册主次设备号 <应用程序通过设备号找到设备>
  3. 驱动设备文件 <应用程序访问驱动的方式> 1、手动创建 (mknod)2、程序自动创建
  4. file_operations <驱动对硬件的读、写、释放等>
  5. 模块卸载函数 module_exit

在这里插入图片描述
在这里插入图片描述

二、举例详解

1、第一种向内核注册字符设备

#include <linux/module.h> // module_init module_exit
#include <linux/init.h>    // __init __exit
#include <linux/fs.h>

#define MYMAJOR 200
#define MYNAME    "LED_DEVICE"

 //int (*open) (struct inode *, struct file *);

//open函数的格式是上面的格式:

static int led_dev_open(struct inode *inode, struct file *file)

{
  printk(KERN_INFO "led_dev_open open\n");
}

//release函数的原型是:int (*release) (struct inode *, struct file *);

static int led_dev_close(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "led_dev_close close\n");
}

static const struct file_operations led_dev_fops{
  .opne = led_dev_open,
  .release = led_dev_close,
}

static int __init leddev_init(void)
{
  int ret = -1;
  printk(KERN_INFO "leddev_init");
 
  ret = register_chrdev(MYMAJOR, MYNAME, &led_dev_fops);
  if(ret) {
    printk(KERN_ERR "led devices rigister failed");
    retunt -EINVAL;
  }

  printk(KERN_INFO "led regist sucess");
  return 0;
}

static int __exit leddev_exit(void)
{
  printfk(KERN_INFO "led device exit");
  unregister_chrdev(MYMAJOR, NAME)}

module_init(leddev_init);module_exit(leddev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("bhc");				// 描述模块的作者
MODULE_DESCRIPTION("led test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

注:
通过对驱动的流程进行分析,以上代码中缺少对设备节点的创建,也就是说,上边的代码,应用程序是没有方法进行访问和操作的,这时,我们可以通过手动的方式进行处理,即使用mknod进行创建,

应用调用驱动是通过驱动设备文件来调用驱动的,我们首先要用mknod /dev/xxx c 主设备号 次设备号 命令来创建驱动设备文件


安装好驱动以后,主设备号可以在/proc/devices文件中查看,但是由于不同的设备主设备号占用的不一样,有时候需要系统来自动分配

主设备号,这个如何实现呢:

我们可以在register_chrdev函数的major变量传参0进去,因为这个函数的返回值为主设备号,所以我们定义一个全局变量来接受这个值即可

static int mymajor;

//注册的时候

mymajor = register_chrdev(0, MYNAME, &ded_dev_fops); # 返回的是自动分配的主设备号

//释放的时候

unregister_chrdev(mymajor, MYNAME);

这样即可;

register_chrdev(major, name, struct file_openrations) # 注册设备号,缺点是只能注册主设备号
unregister_chrdev(major, name) # 注销设备号

1、第二种向内核注册字符设备

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <plat/map-base.h>
#include <plat/map-s5p.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <asm/io.h>
#include <linux/cdev.h>

//#define MYMAJOR            200
//#define MYNAME            "LED_DEVICE"
#define MYDEV             250
#define LED_COUNT        1

#define GPJ0_PA_base        0xE0200240        
#define GPJ0CON_PA_OFFSET    0x0

struct cdev my_led_cdev;

unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;

static char kbuf[100];
static int mymojor;

static int led_dev_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_dev open\n");
    
    return 0;
}

static int led_dev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_dev close\n");
    
    return 0;
}

ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    int ret = -1;
        
    ret = copy_to_user(buf, kbuf, sizeof(kbuf));
    if(ret) {
        printk(KERN_ERR "kernel led read error\n");
    }
    printk(KERN_INFO "led device read success\n");
}

static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
    int ret = -1;
    
    //首先把kbuf清零
    memset(kbuf, 0, sizeof(kbuf));    
    ret = copy_from_user(kbuf, user_buf, count);
    if(ret) {
        printk(KERN_ERR "kernel led write error\n");
        return -EINVAL;
    }
 
    printk(KERN_INFO "led device write success\n");
    
    if (kbuf[0] == '1') {
        *pGPJ0CON = 0x11111111;
        *(pGPJ0CON + 1) = ((0<<3) | (0<<4) | (0<<5));
    }
    
    if (kbuf[0] == '0') {
        *(pGPJ0CON + 1) = ((1<<3) | (1<<4) | (1<<5));
    }  
    return 0;
}

static const struct file_operations led_dev_fops = {
    .open = led_dev_open,
    .write = led_dev_write,
    .read = led_dev_read,    
    .release = led_dev_release,
    .owner = THIS_MODULE,
};

// 模块安装函数
static int __init leddev_init(void)
{    
    int err = 0;
    printk(KERN_INFO "led_device init\n");
    
    
    //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
    err = register_chrdev_region(MKDEV(MYDEV,0), LED_COUNT, "MY_LED_DEV");
    
    if(err)
    {
        printk(KERN_ERR " register_chrdev_region failed\n");
        
        return -EINVAL;
    }
    
    printk(KERN_INFO "leddev_dev regist success\n");
    
    cdev_init(&my_led_cdev, &led_dev_fops);
    
    cdev_add(&my_led_cdev, MKDEV(MYDEV,0), LED_COUNT);
    
    
    if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) {
        return -EINVAL;
    }
    
    pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4);
        
    return 0;
}

// 模块下载函数
static void __exit leddev_exit(void)
{
    printk(KERN_INFO "leddev_dev  exit\n");
    
    //注销led设备驱动
    cdev_del(&my_led_cdev);
    
    unregister_chrdev_region(MKDEV(MYDEV,0), LED_COUNT);
    
    iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET);
    
    release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8);
    
    printk(KERN_INFO "leddev_dev  unregist success\n");   
}

module_init(leddev_init);
module_exit(leddev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("bhc");                 // 描述模块的作者
MODULE_DESCRIPTION("led test");       // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

注:

(1)第二种注册函数

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

以上是第一种注册字符设备的方式,这种方式无法设置次设备号。本次的设备如下所示:

int register_chrdev_region(dev_t from, unsigned count, const char *name) # 注册设备
void cdev_init(struct cdev *, const struct file_operations *);  # 初始化函数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)  # 添加设备函数
void cdev_del(struct cdev *p)   # 删除设备函数
void unregister_chrdev_region(dev_t from, unsigned count)  # 卸载设备

(2)主次设备号注册

linux内核为我们提供了三个宏来确定from、主设备号、次设备号

MKDEV、MAJOR、NIMOR,这么是这三个宏在linux内核中定义的用法:

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

比如说我们的主设备号为250 第一个次设备号为0,那么from应该赋值的参数为MKDEV(250, 0)

知道dev的话 主设备号为 MAJOR(dev)、次设备号为:MINOR(dev)


(3)内核和驱动之间的数据交互

static inline long copy_to_user(void __user *to, const void *from, unsigned long n);     #  从驱动到用户空间
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)   #  从用户空间到驱动

(4)虚拟空间映射

下面我们的低层驱动开始真正的操作硬件了:

在操作硬件的时候,我们会用到硬件的寄存器,因为我们之前在逻辑程序中使用的物理地址来直接写的,而我们在开发板上移植好内核以后

我们的内核程序就是运行在虚拟地址上了,所以我们要看一下我们的linux内核中虚拟地址跟物理地址是如何映射的

动态虚拟地址:当我们要使用这个寄存器的物理地址的时候,不用事先建立好的页表,而是给物理地址动态的分配一个虚拟地址,操作的时候直接使用这个动态分配的虚拟地址

操作物理地址即可,使用完以后取消映射即可;

使用动态虚拟地址映射首先:

1:建立映射

使用request_mem_region向内核申请虚拟地址空间;

#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)

request_mem_region实际上是一个宏,真正调用的是 __request_region这个函数;

request_mem_region宏需要三个参数:start:启示的物理地址,n长度,name

申请成功则返回0;

ioremap

#define ioremap(cookie,size) __arm_ioremap(cookie, size, MT_DEVICE)

也是一个宏,调用的是内核函数__arm_ioremap

这个宏需要两个参数起始物理地址以及 长度;

2:使用完以后我们首先要消除映射

取消映射iounmap宏

#define iounmap(cookie) __iounmap(cookie)

接受一个参数,起始物理地址;

然后在消除分配的虚拟地址

使用release_mem_region

#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))

这个宏只需要两个参数即可一个是起始物理地址,一个长度;

3、第三种向内核注册字符设备

alloc_chrdev_region函数原型如下,它是我们用来向内核申请主设备号时用的,与register_chrdev_region不同的是,alloc_chrdev_region是让内核分配给我们一个尚未使用的主设备号,不是由我们自己指定的,该函数的四个传参意义如下:

dev :alloc_chrdev_region函数向内核申请下来的设备号

baseminor :次设备号的起始

count: 申请次设备号的个数

name :执行 cat /proc/devices显示的名称

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
{
	struct char_device_struct *cd;
	cd = __register_chrdev_region(0, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	*dev = MKDEV(cd->major, cd->baseminor);
	return 0;
}

cdev的使用:

1.执行cdev_init函数,将cdev和file_operations关联起来

2.使用cdev_add函数,将cdev和设备号关联起来

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

例子:

#include <linux/module.h>       // module_init  module_exit
#include <linux/init.h>         // __init   __exit
#include <linux/cdev.h>
#include <linux/fs.h>
#define MYMAJOR     200
#define MYNAME      "chardev_test_byHc"
 
 
//DEVICE ID
dev_t devid;
 
static int chardev_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "chardev open\n");
    return 0;
}
 
static ssize_t chardev_read(struct file *file, char __user *buf,
          size_t size, loff_t *ppos)
{
    return 0;
}
 
 
static const struct file_operations chardev_fops = { 
 
    .open = chardev_open,
    .read = chardev_read,
};
 
static struct
{
    struct cdev cdev;
}chardev;
 
static int __init mymodule_init(void)
{   
    int ret = 0;
    printk(KERN_INFO "chrdev_init helloworld init\n");
    cdev_init(&chardev.cdev,&chardev_fops);
        
    alloc_chrdev_region(&devid,2,255,MYNAME);
    printk(KERN_INFO "MAJOR Number is %d\n",MAJOR(devid));
    printk(KERN_INFO "MINOR Number is %d\n",MINOR(devid));
    cdev_add(&chardev.cdev,devid,255);
        
    return 0;
}
static void __exit mymodule_exit(void)
{
    printk(KERN_INFO "chrdev_exit helloworld exit\n");
    cdev_del(&chardev.cdev);
    unregister_chrdev_region(devid,255);
 
}
 
 
module_init(mymodule_init);
module_exit(mymodule_exit);
 
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");              // 描述模块的许可证
MODULE_AUTHOR("Hanc");              // 描述模块的作者
MODULE_DESCRIPTION("module test");  // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");          // 描述模块的别名信息
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绛洞花主敏明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值