Development Notes for embedded linux Driver

1, Mapping from linux physical address to virtual address

linux cannot directly operate the physical address. If you need to operate the hardware, you need to convert the physical address into a virtual address first. Because linux enables MMU, you cannot directly operate the physical address.
1. What are the benefits of enabling MMU?
(1) Make virtual addresses possible
(2) It can make the system more secure. With MMU, the memory seen by the upper application is virtual memory, and the application cannot directly access the hardware, so as to ensure the system security.
2. MMU is very complex. How to complete the conversion from physical address to virtual address?
The kernel provides related functions.
IO under include / ASM generic h
ioremap: convert physical addresses to virtual addresses. (establish mapping)
phys_addr_t offset: the starting address of the mapped physical address
size_t size: how much memory space do you want to map
Return value: returns the first address of the virtual address
Failed: NULL returned.

 static inline void __iomem *ioremap(phys_addr_t offset, size_t size)
 {
    return (void __iomem *)(unsigned long)offset;
 }

iounmap: convert virtual address to physical address. (unmap)
__ iomem *addr: the first address of the unmapped virtual address.

static inline void iounmap(void __iomem *addr)
 {
 }

be careful

The physical address can only be mapped once, and multiple mappings will fail.

4. How to check which physical addresses have been mapped?
cat /proc/iomem

Practice Course

Requirements:
1. Use miscellaneous equipment to complete a drive of the buzzer

2. Complete an upper layer test application.
Application requirements: input parameter 1 is to turn on the buzzer, and input parameter 0 is to turn off the buzzer.

Driver code:

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

#define GPIO5_DR 0x020AC000

unsigned int *vir_gpio_dr;//The first address that stores the mapped virtual address


int misc_open(struct inode *inode,struct file *file)
{
    printk("hello misc_open\n");
    
    return 0;
}

int misc_close(struct inode *inode,struct file *file)
{
    printk("bye bye\n");
    
    return 0;
}


int misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
    char kbuf[64] = "hello";

    if(copy_to_user(ubuf,kbuf,sizeof(kbuf))!=0)
    {
        printk("copy to user error\n");
        return -1;
    }  
    return 0;

}

int misc_write(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
   char kbuf[64] = {0};

    if(copy_from_user(kbuf,ubuf,size)!=0)  //ubuf is transferred in and saved in kbuf
    {
        printk("copy_from_user\n");
        return -1;
    }  
    printk("kbuf is %s\n",kbuf);

    if (kbuf[0] == 1)
        *vir_gpio_dr |= (1 <<1);//open
    else if (kbuf[0] == 0)
        *vir_gpio_dr &= ~(1 <<1);//close
    
    return 0;

}

struct file_operations  misc_fops ={
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_close,
    .read = misc_read,
    .write = misc_write
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops
};

static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);//register
    if (ret<0)
    {
        printk("misc_register is error\n");
        return -1;
    }
    printk("misc_register is successful\n");
   

    vir_gpio_dr = ioremap(GPIO5_DR,4);

    if(vir_gpio_dr == NULL) 
    {
        printk("GPIO5_DR ioremap error\n");
        return -EBUSY;
    }
    printk("GPIO5_DR ioremap ok\n");
      return 0;

}
static int misc_exit(void)
{
    misc_deregister(&misc_dev);//uninstall
    printk("bye bye");
    iounmap(vir_gpio_dr);
    return 0;
}

module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

Application code

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd;

    char buf[64] = {};

    fd = open("/dev/hello_misc",O_RDWR);
    if (fd < 0) 
    {
        printf("open error\n");
        return fd;
    }

    buf[0] = atoi(argv[1]);
    
    write(fd, buf, sizeof(buf));
    
    close(fd);
    
    return 0;
}

Drive module transmission parameters

1. What are drive parameters
Driving parameters is to pass parameters to the driver.
give an example:
insmod beep.ko a=1

2. What is the role of driving parameters?
(1) Set driver related parameters, such as buffer size
(2) Set the security check of the kernel to prevent the written driver from being stolen by others

3. How to pass parameters to the driver?
(1) Pass ordinary parameters, such as char and int
Function:
module_param(name,type,perm);
Parameters:
Name the name of the parameter to be passed in
Type: type
perm: permission to read and write parameters

static int a;

module_param(a,int,S_IRUSR);

static int hello_init(void)
{
    printk("a=%d\n",a);
    
    return 0;
}

(2) Pass array
Function:
module_param_array(name,type,numo,perm);
Parameters:
name: parameters to be passed
Type: parameter type passed
numo: actual number of incoming
perm: permission to read and write parameters

static int b[5];

static int count;

module_param(a,int,S_IRUSR);

module_param_array(b,int,&count,S_IRUSR);

static int hello_init(void)
{
    int i;
    for (i=0;i<count;i++)
    {
        printk("b[i]=%d\n",i,b[i]);

    }

    printk("count = %d\n",count);

    printk("a=%d\n",a);

    return 0;
}

Pass in parameters using command

insmod parameter.ko b=1,2,3,4,5

(3) What happens if more than one parameter is passed?
Will report an error and crash ~ ~!!!

Application character type equipment number

1. Differences between character equipment and miscellaneous equipment (review)
The master equipment number of miscellaneous equipment is fixed, which is 10.
The main equipment number of character equipment is not fixed, so you need to assign the equipment number yourself or the system.
Miscellaneous equipment can automatically generate equipment nodes.
Character devices need to generate device nodes manually.

2. Two methods for registering character class device numbers.
The first method: statically assign an equipment number
In include / Linux / Fs H function:

int register_chrdev_region(dev_t, unsigned, const char *);

It is necessary to clearly know which equipment numbers in the system are not used.
Parameters:
dev_ Tthe starting value of the device number. The type is dev_t.
unsigned: number of secondary equipment numbers.
const char *: the name of the equipment number.
Return value: 0 for success and non-0 for failure.

dev_t is used to save the device number. It is a 32-bit data type. Defined in Linux / types H medium.
The upper 12 digits are used to save the main equipment number.
The lower 20 digits are used to save the secondary equipment number.

Linux provides several macro definitions to manipulate device numbers.

//The number of digits of the secondary equipment number is 20 in total
#define MINORBITS   20	
//Mask of secondary device number
#define MINORMASK   ((1U << MINORBITS) - 1)
//dev_ Obtain the main equipment number from t
#define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS))
//dev_ Obtain the secondary equipment number from t
#define MINOR(dev)  ((unsigned int) ((dev) & MINORMASK))//
//The main device number and the secondary device number form dev_t type. The first parameter is the primary equipment number and the second parameter is the secondary equipment number.
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

Second: dynamic allocation
The functions used are:

int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);

Parameters:
First: save the generated equipment number.
Second: the first secondary device number of the request, usually 0.
Third: the number of equipment numbers applied continuously.
Fourth: equipment name.
Return value: 0 for success and non-0 for failure.
Using dynamic allocation will give priority to the number between 255-234.

3. Logout equipment number
Parameter 1: assign the starting address of the equipment number.
Parameter 2: number of requested equipment numbers.

void unregister_chrdev_region(dev_t,unsigned);

The driver code is written as follows: there are two device number allocation methods (static allocation and dynamic allocation)

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

static int major_num,minor_num;

#define DEVICE_ Number 1 / / number of assigned device numbers
#define DEVICE_SNAME "schrdev" / / name of main equipment number
#define DEVICE_ANAME "achrdev" / / name of secondary device number


#define DEVICE_MIN_NUMBER 0 / / starting position of secondary equipment number

module_param(major_num,int,S_IRUSR);
module_param(minor_num,int,S_IRUSR);


static int hello_init(void)
{

    dev_t dev_num;
extern void unregister_chrdev_region(dev_t, unsigned);

    int ret;

    printk("major_num = %d\n",major_num);

    printk("minor_num = %d\n",minor_num);    

    if (major_num)//The main equipment number is passed in, and the static method
    {
        dev_num = MKDEV(major_num,minor_num);

        ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
        if (ret<0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region ok\n");
    }
    else //The main equipment number is not passed in. The dynamic method is used to allocate the equipment number
    {
        ret = alloc_chrdev_region(&dev_num,DEVICE_MIN_NUMBER,DEVICE_NUMBER,DEVICE_ANAME);
        if (ret<0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region ok\n");

        major_num = MAJOR(dev_num);

        minor_num = MINOR(dev_num);

        printk("major_num = %d\n",major_num);

        printk("minor_num = %d\n",minor_num);

    }
    
    return 0;
}

static int hello_exit(void)
{
    unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);//Logout equipment number
    printk("bye bye");
    return 0;
}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");


It is recommended to apply for equipment number by dynamic allocation. Static allocation is easy to conflict when used by multiple people.

2, Register character devices

Register miscellaneous equipment
misc_register(&misc_dev);
Write off miscellaneous equipment
misc_deregister(&misc_dev);

cdev structure: the structure that describes the character device
In include / Linux / CDEV H medium

12 struct cdev {
 13     struct kobject kobj;
 14     struct module *owner;
 15     const struct file_operations *ops;
 16     struct list_head list;
 17     dev_t dev;
 18     unsigned int count;
 19 };

Step 1: define a cdev structure

Step 2: use cdev_init function initializes cdev structure members

void cdev_init(struct cdev *, const struct file_operations *);

Parameters:
First: cdev to initialize
Second: file operation set
cdev->ops = fops;// In fact, it is to write the file operation set to OPs.
Step 3: use CDEV_ The add function registers with the kernel

int cdev_add(struct cdev *, dev_t, unsigned);

Parameter 1: structure pointer of cdev
Parameter 2: equipment number
Parameter 3: quantity of secondary equipment number
Unregister character device:

 void cdev_del(struct cdev *);

After the character device registration is completed, the device node will not be generated automatically. You need to use the mknod command to create a device node.
Format:
mknod name type primary equipment number secondary equipment number
give an example:
mknod /dev/test c 247 0

Driver code writing

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

struct cdev cdev;

#define DEVICE_NUMBER  1
#define DEVICE_SNAME "schrdev"
#define DEVICE_ANAME "achrdev"  

#define DEVICE_MIN_NUMBER 0 

static int major_num,minor_num;

module_param(major_num,int,S_IRUSR);
module_param(minor_num,int,S_IRUSR);

int chrdev_open(struct inode *inode, struct file *file)
{
    printk("chrdev_open\n");
    
    return 0;
}
struct file_operations chrdev_ops = 
{
    .owner = THIS_MODULE,
    .open = chrdev_open

};

static int hello_init(void)
{

    dev_t dev_num;

    int ret;

    printk("major_num = %d\n",major_num);

    printk("minor_num = %d\n",minor_num);    

    if (major_num)//The main equipment number is passed in, and the static method
    {
        dev_num = MKDEV(major_num,minor_num);

        ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
        if (ret<0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region ok\n");
    }
    else //The main equipment number is not passed in. The dynamic method is used to allocate the equipment number
    {
        ret = alloc_chrdev_region(&dev_num,DEVICE_MIN_NUMBER,DEVICE_NUMBER,DEVICE_ANAME);
        if (ret<0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region ok\n");

        major_num = MAJOR(dev_num);

        minor_num = MINOR(dev_num);

        printk("major_num = %d\n",major_num);

        printk("minor_num = %d\n",minor_num);

    }
    
    cdev.owner = THIS_MODULE;

    cdev_init(&cdev,&chrdev_ops);//Character device initialization

    cdev_add(&cdev,dev_num,DEVICE_NUMBER);//Registered character device

    return 0;
}

static int hello_exit(void)
{
    unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);

    cdev_del(&cdev);

    printk("bye bye");

    return 0;
}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");


Application code writing

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd;

    char buf[64] = {};


    fd = open("/dev/test",O_RDWR);
    if (fd < 0) 
    {
        printf("open error\n");
        return fd;
    }

    //buf[0] = atoi(argv[1]);

    //read(fd, buf, sizeof(buf));

    //printf("buf is %s\n",buf);

    //write(fd, buf, sizeof(buf));

    //close(fd);

    return 0;

}

Phenomenon: after running the app, print the driver open call result.

3, Automatically create device nodes

In the previous experiment, after the module was loaded with insmod command, the device node was created manually through mknod command. In order to facilitate the operation, learn how to automatically create the device node. When the module is loaded, the corresponding device file is automatically created in the / dev directory.
1. How to automatically create a device node.
mdev is used in embedded linux to realize the automatic creation and deletion of device nodes.
2. What is mdev?
It is a simplified version of udev and a program carried in busybox. It is suitable for embedded systems.
3. What is udev?
A tool that can update device files according to the status of hardware devices in the system, including the creation and deletion of files. Device files are usually stored in the / dev directory. After using udev, only the real devices in the system will be included in the / dev directory. Udev is generally used on PC.
4. How to automatically create a device node?
There are two steps to automatically create a device node
Step 1: use class_ The create function creates a class of class.
Step 2: use device_ The create function creates a device under the class we created.
5. Create and delete class functions
Generally, two functions are used to complete the creation and deletion of device nodes. First, create a class structure,
The class structure is defined in include / Linux / device H inside. class_create is a class creation function, class_create is a macro definition, which is as follows:

 #define class_create(owner, name)       \
({                      \
    static struct lock_class_key __key; \
     __class_create(owner, name, &__key);    \
 })

There are two parameters in total. The parameter owner is generally THIS_MODULE, parameter name is the name of the class. The return value points to the pointer of the structure class, that is, the created class.
When unloading the driver, you need to delete the class. The class deletion function is class_destory. The prototype functions are as follows:
void class_destory(struct class *cls);
The parameter cls is the class to be deleted.

Driver code:
After successful registration, a class will be generated under / sys/class

#define DEVICE_CLASS_NAME "chrdev_class"

static int hello_init(void)
{

    dev_t dev_num;

    int ret;

    printk("major_num = %d\n",major_num);

    printk("minor_num = %d\n",minor_num);    

    if (major_num)//The main equipment number is passed in, and the static method
    {
        dev_num = MKDEV(major_num,minor_num);

        ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
        if (ret<0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region ok\n");
    }
    else //The main equipment number is not passed in. The dynamic method is used to allocate the equipment number
    {
        ret = alloc_chrdev_region(&dev_num,DEVICE_MIN_NUMBER,DEVICE_NUMBER,DEVICE_ANAME);
        if (ret<0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region ok\n");

        major_num = MAJOR(dev_num);

        minor_num = MINOR(dev_num);

        printk("major_num = %d\n",major_num);

        printk("minor_num = %d\n",minor_num);

    }
    
    cdev.owner = THIS_MODULE;

    cdev_init(&cdev,&chrdev_ops);//Character device initialization

    cdev_add(&cdev,dev_num,DEVICE_NUMBER);//Registered character device

    class = class_create(THIS_MODULE,DEVICE_CLASS_NAME);
    return 0;
}


static int hello_exit(void)
{
    unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);

    cdev_del(&cdev);

    class_destory(class);

    printk("bye bye");

    return 0;
}

6. Create device function
After creating a class using the functions in the previous section, use device_ The create function creates a device under this class. device_ The prototype of the create function is as follows:

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

device_create is a variable parameter function,
Parameter 1: under which device is it created
Parameter 2: parent device; generally empty
Parameter 3: devt is the device number
Parameter 4: drvdata is the data that may be used by the device, which is generally NULL;
Parameter 5: fmt device name. If fmt=xxx is set, the device file / dev/xxx will be generated.
The return value is the created device.

Similarly, when unloading the driver, you need to delete the created device, and the device deletion function is device_destory,
The prototype functions are as follows:

 void device_destroy(struct class *cls, dev_t devt);

The parameter class is the class of the device to be deleted, and the parameter devt is the device number to be deleted.

Driver code:

static int hello_init(void)
{
   .
   .
   .
   .
   .
   .
    
    cdev.owner = THIS_MODULE;

    cdev_init(&cdev,&chrdev_ops);//Character device initialization

    cdev_add(&cdev,dev_num,DEVICE_NUMBER);//Registered character device

    class = class_create(THIS_MODULE,DEVICE_CLASS_NAME);

    device = device_create(class,NULL,dev_num,NULL,DEVICE_NODE_NAME);

    return 0;
}

Drive unloading:

static int hello_exit(void)
{
    unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);//Equipment number deletion

    cdev_del(&cdev);//Character device uninstall

    device_destroy(class, dev_num);//Device uninstall
  
    class_destroy(class);//Class unloading

    printk("bye bye");

    return 0;
}

Character equipment and miscellaneous equipment review

Miscellaneous equipment drive frame
Character device driver framework

Keywords: Linux Single-Chip Microcomputer ARM

Added by CashBuggers on Thu, 03 Mar 2022 05:28:51 +0200