ARM-驱动/总结一

news2024/9/19 15:25:59

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;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/674664.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatGPT 指南:秒变 Excel 大神

Excel 是一款功能强大的电子表格软件&#xff0c;而 ChatGPT 则是一种智能语言模型&#xff0c;可以为 Excel 用户提供帮助和指导。本文将探讨 Excel 与 ChatGPT 的关系&#xff0c;并从初级、中级和高级 Excel 用户三个层次&#xff0c;介绍如何利用 ChatGPT 来提升 Excel 技能…

leetcode416. 分割等和子集(动态规划-java)

分割等和子集 leetcode416. 分割等和子集题目描述 暴力递归代码演示 动态规划解题思路代码演示 动态规划专题 leetcode416. 分割等和子集 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/partition-equal-subset-sum 题目…

高级数据结构——平衡二叉树(AVL树)

目录 1. 底层结构 2. AVL数的概念 3. AVL树节点的定义 4. 基本框架 5. AVL树的插入 6. AVL树的旋转 6.1 左单旋 6.2 右单旋 6.3 左右双旋 6.4 右左双旋 7. AVL树的验证 8. AVL树的查找 9. AVL树的删除 10. AVL树的性能 11. 总代码 11.1 AVLTree 11.2 Test.c…

mac本地创建ssh key连接github

起因 今天克隆自己github上面的笔记到新电脑上&#xff0c;用http连接进行克隆&#xff0c;然后要我输入账号密码&#xff0c;输入完报了个提示“remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.”…

JavaScript 手写代码 第一期

文章目录 1.为什么要手写代码&#xff1f;2.手写代码2.1 手写Object.create()方法2.1.1 基本使用2.1.2 使用实例2.1.3 手写实现 2.2 手写实现instanceof方法2.2.1 基本使用2.2.2 使用实例2.2.3 手写实现 2.3 手写实现new操作符2.3.1 基本使用2.3.2 使用实例2.3.3 手写实现 1.为…

分享一个下载按钮

先看效果&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>下载按钮</title><link href"https://fonts.googleapis.com/css2?familyHind&amp;d…

Redisson源码-单线程加解锁流程

Redisson源码-单线程加解锁流程 以下源码分析基于redisson-3.17.6版本&#xff0c;不同版本源码会有些许不同需注意。 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.17.6</version>&l…

推荐5 款好用的 Linux 音乐播放器

目前 Linux 上有几十个音乐播放器&#xff0c;这使得找到一个最好用的变成很困难。之前我们已经回顾了其中的一些播放器&#xff0c;如 Cantata&#xff0c;Exaile&#xff0c;甚至不那么出名的 Clementine&#xff0c;Nightingale 和 Quod Libet。 在本篇文章中我将涵盖更多的…

python学习——pandas数据处理 时间序列案例 matplotlib绘图案例

目录 pandas数据处理1.合并数据1) 堆叠合并2) 主键合并3) 重叠合并 2.分组和聚合3.索引和符合索引4.去除重复值5.处理缺失值6.处理离群值7.标准化数据1) 离差标准化函数2) 标准差标准化函数3) 小数定标差标准化函数 8.转换数据--离散处理9.时间序列【案例】时间序列案例案例1&a…

C++测试

开始对C嘎嘎下手&#xff01; 1.有关char数组 定义长度为5&#xff0c;但是实际长度是定义长度减1 突然就想到计网安全中的栈溢出问题了&#xff0c;C语言是不检查你是否越界的&#xff0c;如果通过让实参溢出覆盖掉原程序的返回地址&#xff0c;通过精心控制是可以让计算机执…

高级数据结构——红黑树

目录 1. 红黑树的概念 2. 红黑树的性质 3. 红黑树 6. 红黑树的验证 7. 红黑树的删除 8. 红黑树与AVL数的比较 9. 红黑树的应用 10. 完整代码 10.1 RBTree.h 10.2 test.cpp 1. 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存…

49天精通Java,第37天,可变参数列表

目录 一、可变参数列表二、可变参数列表的优缺点1、优点2、缺点 三、可变参数列表的适用场景1、函数重载2、命令行参数解析3、集合操作4、函数式编程 大家好&#xff0c;我是哪吒。 &#x1f3c6;本文收录于&#xff0c;49天精通Java从入门到就业。 全网最细Java零基础手把手…

SpringBoot 如何使用 @ResponseStatus 注解处理异常状态码

SpringBoot 如何使用 ResponseStatus 注解处理异常状态码 在 SpringBoot 应用程序中&#xff0c;异常处理是一个非常重要的话题。当应用程序出现异常时&#xff0c;我们需要对异常进行处理&#xff0c;以保证应用程序的稳定性和可靠性。除了使用异常处理器外&#xff0c;Sprin…

重新理解微服务之终究绕不过这4个坎之(一)

写在前头 大家曾经有没有遇过日常技术交流的时候&#xff0c;会讨论某某技术之间的关系是什么&#xff0c;某些技术是否应该用到微服务。我相信热爱技术交流的您&#xff0c;就算不是在微服务这里领域&#xff0c;或多或少都会跟其他同行会做一些争议话题的探讨&#xff0c;而…

华为OD机试真题B卷 JavaScript 实现【字符串分隔】,附详细解题思路

一、题目描述 输入一个字符串&#xff0c;请按长度为8拆分每个输入字符串并进行输出&#xff0c;长度不是8整数倍的字符串请在后面补数字0&#xff0c;空字符串不处理。 二、输入描述 连续输入字符串(每个字符串长度小于等于100)。 三、输出描述 依次输出所有分割后的长度…

k8s使用ceph存储

文章目录 初始化操作k8s使用ceph rbdvolumePV静态pv动态pv k8s使用cephfsvolume静态pv 初始化操作 ceph创建rbd存储池 ceph osd pool create k8s-data 32 32 replicated ceph osd pool application enable k8s-data rbd rbd pool init -p k8s-dataceph添加授权&#xff0c;需…

指针和数组--指针数组及其应用

目录 一、指针数组用于表示多个字符串 二、指针数组用于表示命令行参数 一、指针数组用于表示多个字符串 一维数组可存储一个字符串&#xff0c;二维数组可存储多个字符串。 二维数组的元素在内存中是连续存放的&#xff0c;存完第一行后&#xff0c;再存第二行&#xff0c;以…

多线程之JUC

写在前面 本文一起看下jdk并发包的相关内容。 1&#xff1a;JUC包提供了哪些功能 先通过包结构看下JUC提供的功能&#xff1a; 接下来分别看下。 1.1&#xff1a;锁 JUC中的锁机制提供了比synchronized&#xff0c;wait/notify更加灵活的同步控制&#xff0c;在java.util.…

大数据基础平台实施及运维进阶

1、完全分布式部署介绍 完全分部式是真正利用多台Linux主机来进行部署Hadoop&#xff0c;对Linux机器集群进行规划&#xff0c;使得Hadoop各个模块分别部署在不同的多台机器上。 2、nameNode HA完全分布式部署 2.1、nameNode切换方法 分别处于Active和Standby中 hadoop可以…

操作系统复习笔记4

1、queueType队列类型 队列中的数据也呈线性排列。虽然与栈有些相似&#xff0c;但队列中添加和删除数据的操作分别是在两端进行的。 线性表有顺序存储和链式存储&#xff0c;队列作为一种特殊的线性表&#xff0c;也同样存在这两种存储方式。 1.1 顺序队列 用数组存储队列…