ARM-驱动/总结一

Linux设备驱动

驱动:能够控制硬件实现特定功能的软件代码就是驱动


ARM裸机驱动和驱动区别?

       ARM裸机驱动是不基于操作系统的软件代码,通常这份代码都是有开发者独立编写完成的。

驱动是基于内核(Linux)架构的基础上的软件代码,不但能够操作底层的硬件,还需要和Linux内核对接,通常Linux是内核开发者已经编写的代码,用户只需要编写驱动即可。


一、Linux内核模块

1. 内核模块三要素:

        入口:资源申请工作,在驱动安装的时候执行

        出口:资源释放工作,写驱动卸载的时候执行

        许可证:驱动要遵从GPL协议(开源)

  2.内核模块的编写方式:(代码块)
//内核的头文件在内核源码目录下通过vi -t xx查找
//ctags -R 创建索引   
//或者在内核目录下 make tags
#include <linux/init.h>
#include <linux/module.h>

//1.入口
static int __init demo_init(void)
{
    //static:修饰的函数只能在当前文件中使用
    //int:这个函数的返回值类型

    //__init:#define __init  __section(".init.text") 
    //vmlinux.lds内核的链接脚本,.init.text这是脚本中的一个段
    //内核源码--->vmlinux.lds--->uImage
    //将驱动的入口函数都放在.init.text段中

    //demo_init:入口函数的名字 led_init uart_init...
    //(void) :入口函数没有参数
    return 0;
}
//2.出口
static void __exit demo_exit(void)
{
    //void类型没有返回值
    //__exit告诉编译器将demo_exit函数放在.exit.text段中
}
module_init(demo_init);
//module_init是内核提供的宏
//告诉内核驱动的入口函数的地址
module_exit(demo_exit);
//告诉内核驱动的出口函数的地址

//3.许可证
MODULE_LICENSE("GPL"); //遵从开源协议
 3.内核模块的编译方式:

        内核模块在编译的时候不能使用gcc直接编译,应为在驱动代码中有依赖内核的源码,

所以驱动的编译要依赖内核,必须使用Makefile才能让驱动编译驱动。

1.内部编译:在内核源码树中进行编译

   Kconfig .config Makefile

 2.外部编译:在内核源码树外进行编译(自己写通用Makefile文件)

     make arch=架构 modname=模块名

arch ?=arm
modname ?=demo

ifeq ($(arch),arm)
#KERNELDIR:指向内核目录的一个变量
	KERNELDIR:= /home/ubuntu/linux-5.10.61               #开发板上可安装的arm格式
else
	KERNELDIR := /lib/modules/$(shell uname -r)/build/   #ubuntu可以安装的x86-64格式
endif

PWD :=$(shell pwd)
#当前路径$(shell pwd)在Makefile的时候起一个终端
#这个终端上执行pwd,将这个命令的结果赋值给PWD变量

all:
	make -C $(KERNELDIR) M=$(PWD) modules
#make -C $(KERNELDIR)
#进入到内核顶层目录下,读取这个目录下的Makefile文件,然后执行make
# M=$(PWD) :指定编译模块的路径为当前路径
# make modules 模块化编译
#进入内核顶层目录下读取Makefile文件,然后进行模块化编译
#通过M指定编译的目录在当前目录
clean:
	make -C $(KERNELDIR) M=$(PWD) clean
#清除编译
obj-m:=$(modname).o
#指定编译的当前目录的的模块名是$(modname)===>$(modname).ko
 4.内核模块的安装和卸载

sudo insmod xxx.ko //安装内核模块

lsmod //查看内核模块

sudo rmmod xxx //卸载驱动模块

 5.内核模块中打印语句的使用printk

5.1 printk的用法格式:

printk(打印级别 "控制格式",参数列表); //指定消息的级别

printk("控制格式",参数列表); //消息的默认级别

注:内核中的printk用法和printf用法除了打印级别之外都是一样的,这个打印级别是用来过滤打印信息

5.2printk过滤信息方式:

消息有消息级别,终端也有终端的级别,只有当消息的级别大于终端的级别的时候消息才会在终端上显示。

cat /proc/sys/kernel/printk

#define KERN_EMERG   "0"    /* system is unusable */
#define KERN_ALERT   "1"    /* action must be taken immediately */
#define KERN_CRIT    "2"    /* critical conditions */                                
#define KERN_ERR     "3"    /* error conditions */
#define KERN_WARNING     "4"    /* warning conditions */
#define KERN_NOTICE  "5"    /* normal but significant condition */
#define KERN_INFO    "6"    /* informational */
#define KERN_DEBUG   "7"    /* debug-level messages */
 6.修改消息的默认级别

 1.修改ubuntu的默认打印级别

        su root

        echo 4 3 1 7 > /proc/sys/kernel/printk

2.修改开发板默认打印级别

        rootfs/etc/init.d/rcS

        在这个脚本的最后一行加上--->echo 4 3 1 7 > /proc/sys/kernel/printk

7. 可以通过命令查看打印信息(主动查看)

dmesg //查看内核从启动到当前这一刻所有的打印信息

sudo dmesg -C或-c //清除打印信息(-C直接清除,-c先回显,在清除)

注:如果是白色的说明消息级别是小于终端级别的,如果是红色的说明消息级别是高于终端级别的

8. 内核模块传参

 sudo insmod demo.ko a=10 b=20

通过modinfo xxx.ko看到的现象是:parm: ih:this is backlight var range[0-255] (int)

#include <linux/init.h>
#include <linux/module.h>

int a=20;
module_param(a,int,0664); // module_param(name, type, perm) 用来接收命令行传递过来的参数
                          //@name:变量名   @type:变量的类型   @perm:权限 (最大的权限是0664)
                    /*当这里的权限不是0的时候,它会在/sys/module/驱动名字目录/parameters/
        目录下产生产生一个以name命名的文件,这个权限就是修饰这个文件权限的*/

//1.入口
static int __init demo_init(void)
{
    printk(KERN_ERR "hello DC21121 everyone!!!\n");
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    printk("a = %d\n",a);
    return 0;
}
//2.出口
static void __exit demo_exit(void)
{
    printk(KERN_ERR "bye DC21121 everyone!!!\n");
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议

注意: 

1.传递char类型的成员 <===不能传递字符类型,只能传递整数

2.传递short类型的成员

3.传递字符串指针(char *)成员 <====字符串中不能有空格

sudo insmod 01module_param.ko ch=97 sh=200 ih=900 sp=hello everyone

#include <linux/init.h>
#include <linux/module.h>

char ch='A';
module_param(ch,byte,0664);

short sh=123;
module_param(sh,short,0664);

int ih=2222;
module_param(ih,int,0664);

char *sp = "hell world";
module_param(sp,charp,0664);
//1.入口
static int __init demo_init(void)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    printk("ch = %d\n",ch);
    printk("sh = %d\n",sh);
    printk("ih = %d\n",ih);
    printk("sp = %s\n",sp);
    return 0;
}
//2.出口
static void __exit demo_exit(void)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    printk("ch = %d\n",ch);
    printk("sh = %d\n",sh);
    printk("ih = %d\n",ih);
    printk("sp = %s\n",sp);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议

查看模块内可传参的变量

modinfo 01module_param.ko

二、字符设备驱动

1.字符设备驱动的架构

2.字符设备驱动相关API

编写字符设备驱动: 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

#define CNAME "mycdev"
int major;

int mycdev_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t mycdev_read(struct file *file, 
 char __user *ubuf, size_t size, loff_t *offs)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t mycdev_write(struct file *file, 
 const char __user *ubuf, size_t size, loff_t *off)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
const struct file_operations fops = {
 .open = mycdev_open,
 .read = mycdev_read,
 .write = mycdev_write,
 .release = mycdev_close,
};
static int __init mycdev_init(void)
{
 //1.注册字符设备驱动
 major = register_chrdev(0,CNAME,&fops);
 if(major < 0){
  printk("register char device driver error\n");
  return major;
 }
 printk("register char device driver success... major = %d\n",major);
 return 0;
}
static void __exit mycdev_exit(void)
{
 //2.注销字符设备驱动
 unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

 3.字符设备驱动的测试流程

1.编译驱动

        将驱动的源文件从windows上拷贝到ubuntu上,然后使用Makefile对它进行编译

        make arch=x86 modname=mycdev

2.安装驱动

        sudo insmod mycdev.ko

        dmesg ====>在入口函数中打印的信息

        cat /proc/devices ====>主设备号和设备名

3.为驱动创建设备节点

        sudo mknod /dev/hello c 240 0

        mknod :创建节点的命令

        /dev/hello :设备节点的路径和设备节点名(任意路径都可以,习惯上放在/dev/)

        c/b : c字符设备 b块设备

        240 :主设备号

        0 :次设备号(0-255任意一个都可以)

注:设备节点是应用程序访问到驱动的一个文件

 4.用户空间和内核空间数据传递

 数据拷贝函数API

5.编写LED的驱动:基于STM32MP157单片机

地址映射

应用LED的操作是通过操作寄存器完成,寄存器的地址是物理地址,而驱动运行在3-4G虚拟内存中,所以如果向在内核中操作LED的寄存器,就将LED的寄存器的地址映射到内核空间。

        可以通过ioremap/iounmap完成地址的映射和取消映射的过程。

 

myled.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "myled.h"

#define CNAME "myled"
int major;
char kbuf[128] = {0};
unsigned int *virt_moder;
unsigned int *virt_odr;
unsigned int *virt_rcc;
int myled_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t myled_read(struct file *file, 
 char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 //如果用户想写的大小大于内核的内存大小,更正用户写的大小
 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_to_user(ubuf,kbuf,size);
 if(ret){ //成功返回0,失败返回未拷贝的字节的个数
  printk("copy data to user error\n");
  return -EIO; //失败返回错误码
 }
 return size; //成功返回拷贝的字节的个数
}
ssize_t myled_write(struct file *file, 
const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 //如果用户想写的大小大于内核的内存大小,更正用户写的大小
 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if(ret){ //成功返回0,失败返回未拷贝的字节的个数
  printk("copy data from user error\n");
  return -EIO; //失败返回错误码
 }
 printk("kernel data = %s\n",kbuf);
 
 if(kbuf[0]=='1'){
  *virt_odr |=(1<<10);   //high
 }else if(kbuf[0]=='0'){
  *virt_odr &=~(1<<10);   //low
 }
 return size; //成功返回拷贝的字节的个数
}
int myled_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); 
 return 0;
}
const struct file_operations fops = {
 .open = myled_open,
 .read = myled_read,
 .write = myled_write,
 .release = myled_close,
};
static int __init myled_init(void)
{
 //1.注册字符设备驱动
 major = register_chrdev(0,CNAME,&fops);
 if(major < 0){
  printk("register char device driver error\n");
  return major;
 }
 printk("register myled driver success... major = %d\n",major);

 //2.映射LED1的地址,并将LED1初始化为熄灭
 virt_moder = ioremap(PHY_LED1_MODER,4);
 if(virt_moder == NULL){
  printk("ioremap moder addr error\n");
  return -ENOMEM;
 }
 virt_odr = ioremap(PHY_LED1_ODR,4);
 if(virt_odr == NULL){
  printk("ioremap odr addr error\n");
  return -ENOMEM;
 }
 virt_rcc = ioremap(PHY_LED1_RCC,4);
 if(virt_rcc == NULL){
  printk("ioremap rcc addr error\n");
  return -ENOMEM;
 }
 *virt_rcc |= (1<<4);  //rcc enable
 *virt_moder &=~(3<<20);
 *virt_moder |=(1<<20);  //output
 *virt_odr &=~(1<<10);   //low
 return 0;
}
static void __exit myled_exit(void)
{
 iounmap(virt_rcc);
 iounmap(virt_odr);
 iounmap(virt_moder);
 //2.注销字符设备驱动
 unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

myled.h 

#ifndef __MYLED_H__
#define __MYLED_H__
#define PHY_LED1_MODER 0x50006000
#define PHY_LED1_ODR   0x50006014
#define PHY_LED1_RCC    0x50000a28
#endif

test.c 

#include <head.h>  // arm-linux-gnueabihf-gcc test.c -I /usr/include 
int main(int argc, const char *argv[])
{
    int fd;
    char buf[128] = {0};
    if ((fd = open("/dev/myled", O_RDWR)) == -1)
    {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    while (1)
    {
        printf("input 0(off),1(on) > ");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = '\0';
        write(fd, buf, sizeof(buf));
    }
    close(fd);
    return 0;
}

 三、字符设备驱动:udev机制

1. 创建设备节点的机制

  • mknod:创建设备节点的命令
  • devfs:是早期linux创建设备节点的机制,创建设备节点的逻辑在内核空间(2.4版本之前)
  • udev:创建设备节点的逻辑在用户空间,从内核2.6版本以后至今
  • mdev:作为uudev机制的轻量级存在,常用于一些嵌入式芯片中

 2.udev机制创建设备节点的原理

1.用户在设备驱动中向上提交目录,在内核会申请一个struct class类型的空间,内部存放了     目录信息,内核会在/sys/class/下创建一个目录

2.我们在设备驱动中向上提交节点信息,在内核中会申请一个struct device类型的空间,内       部存放了我们提交的节点信息,内核会在/sys/class/目录/下创建一个存放节点信息的文件

3.完成上面两步后hotplug会通知udev根据/sys/class/目录/存放节点信息的文件中的信息           在/dev下面创建一个设备文件

 3.创建设备节点相关API

 1.#include<linux/device.h>

struct class * class_create(struct module *owner, const char *name)

功能:向上提交目录信息(在内核中申请一个struct class变量的空间,并初始化)

参数:

        owner:指向一个struct module 类型空间的指针,填写THIS_MODULE(指向当前模块的指针)

        name:目录名 返回值:成功返回申请成功的struct class空间指针

         失败返回错误指针

         //内核顶层预留了4K空间,当class_create函数调用失败,得到的返回值是一个指向这4K预留空间的指针 bool __must_check IS_ERR(__force const void *ptr)

        功能:用于判断指针是否指向内核顶层预留的4K空间

        返回值:在预留空间就返回真,不在就返回假

         long __must_check PTR_ERR(__force const void *ptr)

        功能:根据错误码指针得到一个错误码的绝对值

2.void class_destroy(struct class *cls);

功能:销毁申请的struct class空间

参数: cls:cls_create申请的空间首地址

返回值:无

3. struct device *device_create (struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

功能:向上提交设备节点信息(申请一个struct device类型的空间)

参数: class:class_create申请的空间首地址

parent:指定当前申请的struct device空间的额父节点指针,填NULL

devt:设备驱动的设备号

        MKDEV(major,minor)//根据主设备号和次设备号得到设备号

        MAJOR(dev)//根据设备号得到主设备号

        MINOR(dev)//根据设备号得到次设备号

drvdata:填充到申请的struct device 空间中的一个私有数据,填NULL即可

fmt:设备节点的名字

返回值:成功返回申请的struct device 空间首地址,失败错误指针

4.void device_destroy(struct class *class, dev_t devt)

功能:销毁申请的到的struct device空间

参数: class:class_create申请的空间首地址

devt:设备号

返回值:无

 判断错误码的方法:

 注:宏多条语句的用法

#include <head.h>

int max;
#define MAX(a, b)    \
    do               \
    {                \
        if (a > b)   \
            max = a; \
        else         \
            max = b; \
    } while (0)
//do{}while(0) :可以有多条语句,但是乜有返回值,即使有return也不是宏的返回值

#define MAXX(a, b) ({int tt;if(a>b)tt=a;else tt=b; tt; })
//(())宏:可以有多条语句,最后一句话的结果就是宏的返回值
int main(int argc, const char *argv[])
{
    MAX(100, 200);
    printf("max = %d\n", max);
    printf("max = %d\n",MAXX(1000,200));
    return 0;
}

 自动创建设备节点的实例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"

#define CNAME "myled"
#define LED1_ON   (virt_led1->ODR |=(1<<10)) 
#define LED1_OFF  (virt_led1->ODR &=~(1<<10)) 
#define LED2_ON   (virt_led2->ODR |=(1<<10)) 
#define LED2_OFF  (virt_led2->ODR &=~(1<<10)) 
#define LED3_ON   (virt_led3->ODR |=(1<<8)) 
#define LED3_OFF  (virt_led3->ODR &=~(1<<8)) 

int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;

struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t myled_read(struct file *file, 
 char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_to_user(ubuf,kbuf,size);
 if(ret){ 
  printk("copy data to user error\n");
  return -EIO; 
 }
 return size;
}

//kbuf[2] = {which,status};
//0 led1   1  led2   2  led3
//0 on 1 off
ssize_t myled_write(struct file *file, 
 const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if(ret){ 
  printk("copy data from user error\n");
  return -EIO; 
 }
 switch(kbuf[0]){ //kbuf[0] which
  case LED1:
   //kbuf[1] status
   kbuf[1]==1?LED1_ON:LED1_OFF;
   break;
  case LED2:
   kbuf[1]==1?LED2_ON:LED2_OFF;
   break;
  case LED3:
   kbuf[1]==1?LED3_ON:LED3_OFF;
   break;
  default:
   printk("input arg error,try again\n");
   return -EINVAL;
 }
 return size; 
}
int myled_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); 
 return 0;
}
const struct file_operations fops = {
 .open = myled_open,
 .read = myled_read,
 .write = myled_write,
 .release = myled_close,
};

int all_led_init(void)
{
 virt_led1 = ioremap(PHY_LED1_ADDR,sizeof(gpio_t));
 if(virt_led1 == NULL){
  printk("ioremap led1 addr error\n");
  return -ENOMEM;
 }
 virt_led2 = ioremap(PHY_LED2_ADDR,sizeof(gpio_t));
 if(virt_led2 == NULL){
  printk("ioremap led2 addr error\n");
  return -ENOMEM;
 }
 virt_led3 = virt_led1;

 virt_rcc = ioremap(PHY_RCC_ADDR,4);
 if(virt_rcc == NULL){
  printk("ioremap rcc addr error\n");
  return -ENOMEM;
 }
 *virt_rcc |= (3<<4); // rcc gpioe gpiof enable

 //init led1
 virt_led1->MODER &=~(3<<20);
 virt_led1->MODER |=(1<<20);  //output
 virt_led1->ODR   &=~(1<<10); //led1 off
 //init led2
 virt_led2->MODER &=~(3<<20);
 virt_led2->MODER |=(1<<20);  //output
 virt_led2->ODR   &=~(1<<10); //led2 off
 //init led3
 virt_led3->MODER &=~(3<<16);
 virt_led3->MODER |=(1<<16);  //output
 virt_led3->ODR   &=~(1<<8); //led3 off

 return 0;
}
static int __init myled_init(void)
{
 //1.注册字符设备驱动
 major = register_chrdev(0,CNAME,&fops);
 if(major < 0){
  printk("register char device driver error\n");
  return major;
 }

 printk("register myled driver success... major = %d\n",major);
 //2.led地址映射及初始化
 all_led_init();

 //3.自动创建设备节点
 cls = class_create(THIS_MODULE,"hello");
 if(IS_ERR(cls)){
  printk("class create error\n");
  return PTR_ERR(cls);
 }
 dev = device_create(cls,NULL,MKDEV(major,0),NULL,"myled");
 if(IS_ERR(dev)){
  printk("device create error\n");
  return PTR_ERR(dev);
 }
 return 0;
}
static void __exit myled_exit(void)
{
 device_destroy(cls,MKDEV(major,0));
 class_destroy(cls);

 iounmap(virt_rcc);
 iounmap(virt_led1);
 iounmap(virt_led2);
 unregister_chrdev(major,CNAME);

}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

四、字符设备驱动的内部实现

 1.字符设备驱动框架图

文件存在文件系统中,会有一个标识inode号,基于这个标识找到了struct_inode结构体(保存当前文件信息),struct_inode结构体中有一个struct cdev   *i_cdev类型的字符设备指针,这个指针指向当前驱动对象struct cdev结构体 (字符设备驱动对象结构体)/(设备号是驱动存在内核的标识,也是设备驱动和设备文件关联的纽带),而这个结构体存放有struct file_operations *ops 操作方法结构体指针;接着基于这个struct file_operations *ops 操作方法结构体指针,找到了操作方法 mycdev_open()、mycdev_read()、mycdev_write()、mycdev_close();操作方法回调到mycdev_open(用户层)
————————————————
2.字符设备驱动分步实现流程(API)

#include <linux/cdev.h>

1.字符设备驱动结构体
    struct cdev {
        struct module *owner;             //THIS_MODULE
        const struct file_operations *ops; //操作方法结构体
        struct list_head list;            //构成链表
        dev_t dev;                        //设备号
        unsigned int count;               //设备的个数
    };

2.分配字符设备驱动的对象
    struct cdev cdev;
 struct cdev *cdev = cdev_alloc();
 
 struct cdev *cdev_alloc(void)
    功能:为cdev结构体指针分配内存
    参数:
        @无
    返回值:成功返回cdev的结构体指针,失败返回NULL
        
3.字符设备驱动对象初始化
 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    功能:完成cdev结构体成员的初始化(部分)
    参数:
        @cdev:cdev的结构体指针
        @fops:操作方法结构体指针
    返回值:无
    
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    功能:静态指定设备号
    参数:
         @from:指定的设备号 (主设备号|次设备号 eg: 241<<20|0)
         @count:设备的个数(3)
         @name:设备驱动的名字 cat /proc/devices查看
    返回值:成功返回0,失败返回错误码
         
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
   const char *name)  

    功能:动态申请设备号
    参数:
         @dev :申请到的设备号
         @baseminor:次设备号开始值
         @count:设备的个数(3)
         @name:设备驱动的名字 cat /proc/devices查看
    返回值:成功返回0,失败返回错误码
                 
4.注册字符设备驱动
 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
 功能:注册字符设备驱动
    参数:
         @p:cdev结构体指针
         @dev:设备号
         @count:设备的个数
    返回值:成功返回0,失败返回错误码
-------------------------------------------------------------------------------------
1.销毁字符设备驱动
 void cdev_del(struct cdev *p)  
    功能:销毁字符设备驱动
    参数:
        @p:cdev结构体指针
    返回值:无
2.释放设备号
 void unregister_chrdev_region(dev_t from, unsigned count) 
    功能:释放设备号
    参数:
        @from:设备号
        @count:设备的个数
    返回值:无
3.释放动态申请的内存
     void kfree(void *p)
     功能:释放动态申请的内存
     参数:
         @p:cdev结构体的首地址
     返回值:无     

 分步实现字符设备驱动的实例

 基础框架

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

//定义cdev的结构体指针变量
struct cdev *cdev;
static int __init mycdev_init(void)
{
    //1.分配对象
    //2.对象的初始化
    //3.申请设备号
    //4.字符设备驱动的注册
    //5.自动创建设备节点
    return 0;
}
static void __exit mycdev_exit(void)
{
    //1.销毁设备节点
    //2.销毁字符设备驱动
    //3.销毁设备号
    //4.释放动态申请的cdev内存

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

 mycdev.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define CNAME "mycdev"
//定义cdev的结构体指针变量
struct cdev *cdev;
#if 0
unsigned int major = 0; //动态申请
#else
unsigned int major = 500; //静态指定
#endif
int minor=0;
const int count=3;
struct class *cls;
struct device *dev;

char kbuf[128] = {0};
int mycdev_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
 return 0;
}
ssize_t mycdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_to_user(ubuf,kbuf,size);
 if(ret){ 
  printk("copy data to user error\n");
  return -EIO;
    }
 return size; 
}

ssize_t mycdev_write(struct file *file, 
 const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

 if(size > sizeof(kbuf)) size=sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if(ret){ 
  printk("copy data from user error\n");
  return -EIO; 
 }
 return size; 
}
int mycdev_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); 
 return 0;
}

const struct file_operations fops = {
 .open = mycdev_open,
 .read = mycdev_read,
 .write = mycdev_write,
 .release = mycdev_close,
};

static int __init mycdev_init(void)
{
    int i,ret;
    dev_t devno;
    //1.分配对象
    cdev = cdev_alloc();
    if(cdev == NULL){
        printk("cdev alloc memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    //2.对象的初始化
    cdev_init(cdev,&fops);
    //3.申请设备号
    if(major == 0){
        //动态申请
        ret = alloc_chrdev_region(&devno,minor,count,CNAME);
        if(ret){
            printk("dynamic:alloc device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }else if(major > 0){
        //静态指定
        ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
        if(ret){
            printk("static:alloc device number error\n");
            goto ERR2;
        }
    }
    //4.字符设备驱动的注册
    ret = cdev_add(cdev,MKDEV(major,minor),count);
    if(ret){
        printk("cdev register error\n");
        goto ERR3;
    }
    //5.自动创建设备节点
    cls = class_create(THIS_MODULE,CNAME);
    if(IS_ERR(cls)){
        printk("class create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for(i=0;i<count;i++){
        dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
        if(IS_ERR(dev)){
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }
    return 0; //!!!!!!!这里的return 0千万不要忘记写!!!!!!!!!!!!!!
ERR5:
    for(--i;i>=0;i--){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major,minor),count);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    //1.销毁设备节点
    for(i=0;i<count;i++){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
    //2.销毁字符设备驱动
     cdev_del(cdev);
    //3.销毁设备号
    unregister_chrdev_region(MKDEV(major,minor),count);
    //4.释放动态申请的cdev内存
     kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

五、Linux系统中 ioctl函数的使用

1. ioctl的函数API

 #include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
功能:控制设备
参数:
     @fd ;文件描述符
     @request:命令码
     @... :可写,可不写,如果填写填写地址
返回值:成功返回0,失败返回-1置位错误码 user
————————————————————————————————————————————
struct file_operations kernel
long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg)
{
    //应用层的request传递给cmd
    //应用层的...传递给arg
}

2. ioctl函数的命令码

 ====== ==================================
 bits meaning
 ====== ==================================
 31-30  00 - no parameters: uses _IO macro
    10 - read: _IOR
    01 - write: _IOW
    11 - read/write: _IOWR

 29-16  size of arguments

 15-8   ascii character supposedly
    unique to each driver
 7-0    function #
 ====== ==================================

如果向得到上述的32位表述某种功能的命名码需要通过如下的宏实现 

 3. ioctl的实例

myled.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"

#define CNAME "myled"
#define LED1_ON (virt_led1->ODR |= (1 << 10))
#define LED1_OFF (virt_led1->ODR &= ~(1 << 10))
#define LED2_ON (virt_led2->ODR |= (1 << 10))
#define LED2_OFF (virt_led2->ODR &= ~(1 << 10))
#define LED3_ON (virt_led3->ODR |= (1 << 8))
#define LED3_OFF (virt_led3->ODR &= ~(1 << 8))

int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;

struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
 return 0;
}
ssize_t myled_read(struct file *file,
       char __user *ubuf, size_t size, loff_t *offs)
{
 int ret;
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);

 if (size > sizeof(kbuf))
  size = sizeof(kbuf);
 ret = copy_to_user(ubuf, kbuf, size);
 if (ret)
 {
  printk("copy data to user error\n");
  return -EIO;
 }

 return size;
}

// kbuf[2] = {which,status};
// 0 led1   1  led2   2  led3
// 0 on 1 off
ssize_t myled_write(struct file *file,
     const char __user *ubuf, size_t size, loff_t *off)
{
 int ret;
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);

 if (size > sizeof(kbuf))
  size = sizeof(kbuf);
 ret = copy_from_user(kbuf, ubuf, size);
 if (ret)
 {
  printk("copy data from user error\n");
  return -EIO;
 }

 return size;
}
long myled_ioctl(struct file *file,
     unsigned int cmd, unsigned long arg)
{
 //int which, ret;
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
 switch(cmd){
  case LED1ON:
   LED1_ON;
   break;
  case LED1OFF:
   LED1_OFF;
   break;
 }
 // switch (cmd)
 // {
 // case LED_ON:
 // 	ret = copy_from_user(&which, (void *)arg, sizeof(int));
 // 	if (ret)
 // 	{
 // 		printk("copy data from user error\n");
 // 		return -EINVAL;
 // 	}
 // 	switch (which)
 // 	{
 // 	case LED1:
 // 		LED1_ON;
 // 		break;
 // 	case LED2:
 // 		LED2_ON;
 // 		break;
 // 	case LED3:
 // 		LED3_ON;
 // 		break;
 // 	}
 // 	break;
 // case LED_OFF:
 // 	ret = copy_from_user(&which, (void *)arg, sizeof(int));
 // 	if (ret)
 // 	{
 // 		printk("copy data from user error\n");
 // 		return -EINVAL;
 // 	}
 // 	switch (which)
 // 	{
 // 	case LED1:
 // 		LED1_OFF;
 // 		break;
 // 	case LED2:
 // 		LED2_OFF;
 // 		break;
 // 	case LED3:
 // 		LED3_OFF;
 // 		break;
 // 	}
 // 	break;
 // 	break;
 // }

 return 0;
}
int myled_close(struct inode *inode, struct file *file)
{
 printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
 return 0;
}

const struct file_operations fops = {
 .open = myled_open,
 .read = myled_read,
 .write = myled_write,
 .unlocked_ioctl = myled_ioctl,
 .release = myled_close,
};

int all_led_init(void)
{
 virt_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
 if (virt_led1 == NULL)
 {
  printk("ioremap led1 addr error\n");
  return -ENOMEM;
 }
 virt_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
 if (virt_led2 == NULL)
 {
  printk("ioremap led2 addr error\n");
  return -ENOMEM;
 }

 virt_led3 = virt_led1;

 virt_rcc = ioremap(PHY_RCC_ADDR, 4);
 if (virt_rcc == NULL)
 {
  printk("ioremap rcc addr error\n");
  return -ENOMEM;
 }

 *virt_rcc |= (3 << 4); // rcc gpioe gpiof enable

 // init led1
 virt_led1->MODER &= ~(3 << 20);
 virt_led1->MODER |= (1 << 20); // output
 virt_led1->ODR &= ~(1 << 10);  // led1 off
 // init led2
 virt_led2->MODER &= ~(3 << 20);
 virt_led2->MODER |= (1 << 20); // output
 virt_led2->ODR &= ~(1 << 10);  // led2 off
 // init led3
 virt_led3->MODER &= ~(3 << 16);
 virt_led3->MODER |= (1 << 16); // output
 virt_led3->ODR &= ~(1 << 8);   // led3 off

 return 0;
}
static int __init myled_init(void)
{
 // 1.注册字符设备驱动
 major = register_chrdev(0, CNAME, &fops);
 if (major < 0)
 {
  printk("register char device driver error\n");
  return major;
 }

 printk("register myled driver success... major = %d\n", major);
 // 2.led地址映射及初始化
 all_led_init();

 // 3.自动创建设备节点
 cls = class_create(THIS_MODULE, "hello");
 if (IS_ERR(cls))
 {
  printk("class create error\n");
  return PTR_ERR(cls);
 }
 dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "myled");
 if (IS_ERR(dev))
 {
  printk("device create error\n");
  return PTR_ERR(dev);
 }
 return 0;
}
static void __exit myled_exit(void)
{
 device_destroy(cls, MKDEV(major, 0));
 class_destroy(cls);

 iounmap(virt_rcc);
 iounmap(virt_led1);
 iounmap(virt_led2);
 unregister_chrdev(major, CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

 myled.h

#ifndef __MYLED_H__
#define __MYLED_H__

typedef struct{
 volatile unsigned int MODER;
 volatile unsigned int OTYPER;
 volatile unsigned int OSPEEDR;
 volatile unsigned int PUPDR;
 volatile unsigned int IDR;
 volatile unsigned int ODR;
 volatile unsigned int BSRR;  
}gpio_t;

#define PHY_RCC_ADDR  0x50000a28
#define PHY_LED1_ADDR 0x50006000
#define PHY_LED2_ADDR 0x50007000 
#define PHY_LED3_ADDR 0x50006000

enum{
    LED1,
    LED2,
    LED3
};

#define LED_ON  _IOW('a',0,int)
#define LED_OFF  _IOW('a',1,int)

#define LED1ON  _IO('a',3)
#define LED1OFF  _IO('a',4)
#endif

test.c

#include <head.h>
#include "myled.h"
int main(int argc, const char *argv[])
{
    int fd;
    int which;
    if ((fd = open("/dev/myled", O_RDWR)) == -1)
    {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    while (1)
    {
        // which = LED1;
        // ioctl(fd, LED_ON, &which);
        // sleep(1);
        // ioctl(fd, LED_OFF, &which);
        // sleep(1);
        ioctl(fd, LED1ON);
        sleep(1);
        ioctl(fd, LED1OFF);
        sleep(1);
    }
    close(fd);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_57039874/article/details/131343769