ARM-Driver/Summary 1

Linux device driver

Driver: The software code that can control the hardware to implement specific functions is the driver.


What is the difference between ARM bare metal driver and driver?

       The ARM bare metal driver is software code that is not based on the operating system. Usually this code is written independently by developers.

Drivers are software codes based on the kernel (Linux) architecture. They can not only operate the underlying hardware, but also need to interface with the Linux kernel. Usually Linux is code that has been written by kernel developers, and users only need to write drivers.


1. Linux kernel module

1. Three elements of kernel module:

        Entry: Resource application work, executed during driver installation

        Exit: Resource release work, executed when the write driver is uninstalled

        License: The driver must comply with the GPL agreement (open source)

  2. How to write the kernel module: (code block)
//内核的头文件在内核源码目录下通过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. How to compile the kernel module:

        The kernel module cannot be compiled directly using gcc when compiling. It should be because the driver code has source code that depends on the kernel.

Therefore, the compilation of the driver depends on the kernel, and a Makefile must be used to allow the driver to compile the driver.

1. Internal compilation: Compile in the kernel source tree

   Kconfig .config Makefile

 2. External compilation: Compile outside the kernel source tree (write your own general Makefile)

     make arch=architecture modname=module name

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. Installation and uninstallation of kernel modules

sudo insmod xxx.ko  //Install kernel module

lsmod  //View kernel modules

sudo rmmod xxx  //Uninstall the driver module

 5. Use of print statements in the kernel module printk

5.1 Usage format of printk:

printk(Print level "control format", parameter list); //Specify the level of the message

printk("Control format", parameter list); //Default level of message

Note: The usage of printk and printf in the kernel are the same except for the printing level. This printing level is used to filter the printing information.

5.2printk filter information method:

A message has a message level, and a terminal also has a terminal level. Only when the message level is greater than the terminal level, the message will be displayed on the terminal.

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. Modify the default level of messages

 1. Modify the default printing level of ubuntu

        su root

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

2. Modify the development board’s default printing level

        rootfs/etc/init.d/rcS

        Add --->echo 4 3 1 7 > /proc/sys/kernel/printk to the last line of this script

7. You can view the print information through commands (active view)

dmesg  //View all the print information of the kernel from startup to the current moment

sudo dmesg -C or -c  //Clear print information (-C clears directly, -c echoes first, then clears)

Note: If it is white, it means the message level is lower than the terminal level. If it is red, it means the message level is higher than the terminal level.

8. Kernel module parameter passing

 sudo insmod demo.if a=10 b=20

The phenomenon seen through modinfo xxx.ko is: 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"); //遵从开源协议

Notice: 

1. Pass members of char type <=== Character types cannot be passed, only integers can be passed

2. Pass members of short type

3. Pass the string pointer (char *) member <==== There cannot be spaces in the string

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"); //遵从开源协议

View the variables that can be passed as parameters in the module

modinfo 01module_param.ko

2. Character device driver

1.Character device driver architecture

2. Character device driver related APIs

Write character device driver: 

#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. Character device driver testing process

1. Compile driver

        Copy the source file of the driver from windows to ubuntu, and then use Makefile to compile it

        make arch=x86 modname=mycdev

2.Install the driver

        sudo insmod mycdev.ko

        dmesg ====>Information printed in the entry function

        cat /proc/devices ====>Major device number and device name

3. Create a device node for the driver

        sudo mknod /dev/hello c 240 0

        mknod: command to create nodes

        /dev/hello: The path and name of the device node (any path is acceptable, it is customary to place it in /dev/)

        c/b: c character device b block device

        240: Major device number

        0: Secondary device number (any one from 0 to 255 is acceptable)

Note: The device node is a file that the application accesses to the driver.

 4. User space and kernel space data transfer

 Data copy function API

5. Write LED driver: based on STM32MP157 microcontroller

address mapping

The operation of applying LED is completed by operating the register. The address of the register is the physical address, and the driver runs in 3-4G virtual memory. Therefore, if the LED register is operated in the kernel, the address of the LED register is mapped to the kernel space. .

        The process of address mapping and unmapping can be completed through 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;
}

 3. Character device driver: udev mechanism

1. Mechanism for creating device nodes

  • mknod: command to create device nodes
  • devfs: is the mechanism for creating device nodes in early Linux. The logic of creating device nodes is in the kernel space (before version 2.4)
  • udev: The logic of creating device nodes is in user space, from kernel version 2.6 to the present
  • mdev: As a lightweight version of uudev mechanism, it is often used in some embedded chips.

 2. The principle of creating device nodes by udev mechanism

1. The user submits the directory upward in the device driver, and the kernel will apply for a struct class type space, which stores the directory information. The kernel will create a directory under /sys/class/

2. We submit the node information upwards in the device driver, and apply for a struct device type space in the kernel, which stores the node information we submitted. The kernel will create a space to store the node information under /sys/class/directory/ document

3. After completing the above two steps, hotplug will notify udev to create a device file under /dev based on the information in /sys/class/directory/file that stores node information.

 3. Create device node-related APIs

 1.#include<linux/device.h>

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

Function: Submit directory information upward (apply for a struct class variable space in the kernel and initialize it)

parameter:

        owner : a pointer to a struct module type space, fill in THIS_MODULE (pointer to the current module)

        name: Directory name Return value: Successfully returns the struct class space pointer with successful application

         Failure returns error pointer

        //4K space is reserved at the top level of the kernel. When the class_create function call fails, the return value is a pointer to the 4K reserved space bool __must_check IS_ERR(__force const void *ptr)

        Function: Used to determine whether the pointer points to the 4K space reserved at the top level of the kernel

        Return value: Returns true if the space is reserved, false if no space is reserved.

         long __must_check PTR_ERR(__force const void *ptr)

        Function: Get the absolute value of an error code based on the error code pointer

2.void class_destroy(struct class *cls);

Function: Destroy the applied struct class space

Parameters: cls: the first address of the space requested by cls_create

Return value: None

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

Function: Submit device node information upward (apply for a struct device type space)

Parameters: class: first address of space applied for by class_create

parent: Specify the parent node pointer of the currently applied for struct device space, fill in NULL

devt: the device number of the device driver

        MKDEV(major,minor)//Get the device number based on the major device number and minor device number

        MAJOR(dev)//Get the main device number according to the device number

        MINOR(dev)//Get the minor device number according to the device number

drvdata: a private data filled in the applied struct device space, just fill in NULL

fmt: the name of the device node

Return value: The first address of the applied struct device space is returned successfully, and the error pointer is returned if it fails.

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

Function: Destroy the applied struct device space

Parameters: class:class_create the first address of the space applied for

devt: device number

Return value: None

 How to determine the error code:

 Note: Usage of multiple statements of macro

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

 Automatically create instances of device nodes

#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");

4. Internal implementation of character device driver

 1.Character device driver framework diagram

The file exists in the file system, and there will be an identification inode number. Based on this identification, the struct_inode structure (to save the current file information) is found. In the struct_inode structure, there is a character device pointer of type struct cdev *i_cdev, which points to the current drive object. struct cdev structure (character device driver object structure)/(the device number is the identification of the driver's kernel, and also the link between the device driver and the device file), and this structure stores the struct file_operations *ops operation method structure pointer; Then based on this struct file_operations *ops operation method structure pointer, the operation methods mycdev_open(), mycdev_read(), mycdev_write(), mycdev_close() are found; the operation method calls back to mycdev_open (user layer)
———————— ————————
2. Character device driver step-by-step implementation process (API)

#include <linux/cdev.h>

1. Character device driver structure
    struct cdev {         struct module *owner; //THIS_MODULE         const struct file_operations *ops; //Operation method structure         struct list_head list; //Constitute linked list         dev_t dev; //Device number         unsigned int count; / /number of devices     };





2. Allocate the character device driver object
    struct cdev cdev;
 struct cdev *cdev = cdev_alloc();
 
 struct cdev *cdev_alloc(void)
    function: allocate memory for the cdev structure pointer
    Parameters:
        @None
    return value: successfully return the cdev structure Pointer, return NULL on failure
        
3. Character device driver object initialization
 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    Function: Complete the initialization (part) of cdev structure members
    Parameters:
        @cdev: cdev structure pointer
        @fops : Operation method structure pointer
    Return value: None
    
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    Function: Statically specify the device number
    Parameters:
         @from: Specified device number (major device number | minor device number eg: 241 <<20|0)
         @count: the number of devices (3)
         @name: the name of the device driver cat /proc/devices view
    Return value: 0 is returned on success, error code is returned on failure
         
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
   const char *name)  

    Function: Dynamically apply for device number
    Parameters:
         @dev: applied for device number
         @baseminor: secondary device Number starting value
         @count: the number of devices (3)
         @name: the name of the device driver cat /proc/devices Check
    the return value: 0 is returned on success, error code 4 is returned on failure.
                 
Register character device driver
 int cdev_add(struct cdev *p , dev_t dev, unsigned count)
 Function: Register character device driver
    parameters:
         @p:cdev structure pointer
         @dev: device number
         @count: number of devices
    Return value: 0 is returned on success, error code is returned on failure
----- -------------------------------------------------- -------------------------------
1. Destroy the character device driver
 void cdev_del(struct cdev *p)  
    Function: Destroy character device Driver
    parameters:
        @p: cdev structure pointer
    Return value: none
2. Release device number
 void unregister_chrdev_region(dev_t from, unsigned count) 
    Function: Release device number
    Parameters:
        @from: device number
        @count: device number Number
    Return value: None
3. Release the dynamically applied memory
     void kfree(void *p)
     Function: Release the dynamically applied memory
     Parameters:
         @p: the first address of the cdev structure
     Return value: None     

 An example of implementing a character device driver step by step

 basic framework

#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");

Five, the use of ioctl function in Linux system

1. ioctl function API

 #include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
Function: control device
Parameters:
     @fd; File descriptor
     @request: Command code
     @...: Writable, optional Write, if you fill in the address,
return value: 0 is returned if successful, -1 is returned if failed. Set error code user
———————————————————————————— ————————————————
struct file_operations kernel
long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg)
{     //The application layer request is passed to cmd     //Application layer's...passed to arg }


2. Command code of ioctl function

 ====== ==================================
 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 #
 ====== ==================================

If you want to get the above 32-bit naming code that expresses a certain function, you need to implement it through the following macro 

 3. Example of 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;
}

 

Guess you like

Origin blog.csdn.net/weixin_57039874/article/details/131343769