Linux knowledge sorting

Posted by kujtim on Fri, 24 Dec 2021 07:35:21 +0100

Linux driver

Character device driver framework

Character device is a device that reads and writes bytes one by one according to the byte stream, and the data is read and written in order. For example, our most common lighting, keys, IIC, SPI, LCD, etc. are character devices. The drivers of these devices are called character device drivers.

Character device structure

Using cdev structure to represent a character device in Linux

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;	//Device file operations collection
	struct list_head list;
	dev_t dev;	//Equipment number
	unsigned int count;	//Number of equipment
};

Where dev is the device number, including the primary device number and secondary device number information. The primary device number represents a specific drive, and the secondary device number represents each device using this drive. Using dev in Linux system_ T to define the device number, dev_t is actually an unsigned int type, which is a 32-bit data type. The upper 12 digits are the main equipment number and the lower 20 digits are the secondary equipment number.

Allocation of equipment number

**Static allocation: * * the device number is statically specified by the driver developer through the function register_chrdev_region applies to the kernel for use. If the device number has been used, the application fails.

**Dynamic allocation: * * use alloc before registering character devices_ chrdev_ The region function applies for a device number, and the system will automatically give you an unused device number, so as to avoid conflicts.

To release the device number after unregistering the character device, the device number release function unregister_chrdev_region

Device file operation function set struct file_operations *ops

struct file_operations *ops is a collection of function pointers that define the operations that can be performed on the device

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);//Used to modify the current read / write location of the file
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//Used to read device files.
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//Used to write (send) data to device files
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);//Polling function, used to query whether the device can read and write non blocking
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//Provides the control function for the device, corresponding to the ioctl function in the application
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);//It is used to map the memory of the device to the process space (i.e. user space). Generally, the frame buffer device will use this function, such as LCD driven video memory. After mapping the frame buffer (LCD video memory) to the user space, the application can directly operate the video memory, so there is no need to copy back and forth between user space and kernel space.
	int (*mremap)(struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);//It is used to refresh the data to be processed and refresh the data in the buffer to the disk.
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
};

Character driven initialization steps

1. Define character device structure struct cdev

2. Using void cdev_ The init (struct cdev * cdev, const struct file_operations * FOPS) function initializes the cdev line

3. Using int CDEV_ The add (struct CDEV * P, dev_t dev, unsigned count) function adds a character device to the linux system.

4. Create device node

  1. Create class: use struct class * class_ The create (struct module * owner, const char * name) function creates a class
  2. Create device: use struct device *device_create(....) Function to create a device under a class

5. Set file private data

Each hardware device has some attributes, such as the main device number (dev_t), class (class), device (device), switch state (state), etc. when writing the driver, you can write it as a structure. When writing the driver open function, you can add the device structure as private data to the device file.

/* Equipment structure */
struct test_dev{
	dev_t devid; /* Equipment number */
	struct cdev cdev; /* cdev */
	struct class *class; /* class */
	struct device *device; /* equipment */
	int major; /* Main equipment No */
	int minor; /* Secondary equipment No */
};
struct test_dev testdev;
/* open function */
static int test_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &testdev; /* Set private data */
	return 0;
}

After setting the private data in the open function, read the private data directly in the write, read, close and other functions_ Data to get the equipment structure.

Drive logoff

/* Unregister character device */
cdev_del(&newchrled.cdev);/* Delete cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);//Unregister device number devid
device_destroy(newchrled.class, newchrled.devid);//Delete device
class_destroy(newchrled.class);//Delete class

Complete driver example

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define DTSLED_CNT 			 one 		  	/*  Number of equipment numbers*/
#define DTSLED_NAME 			 "dtsled" 	/*  Name*/
#define LEDOFF  					 0 			/*  Turn off the lights*/
#define LEDON  					 one 			/*  Turn on the light*/

/* Mapped register virtual address pointer */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* dtsled Equipment structure */
struct dtsled_dev{
	dev_t devid;			/* Equipment number 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* class 		*/
	struct device *device;	/* equipment 	 */
	int major;				/* Main equipment No	  */
	int minor;				/* Secondary equipment No   */
	struct device_node	*nd; /* Device node */
};

struct dtsled_dev dtsled;	/* led equipment */

/*
 * @description		: LED On / off
 * @param - sta 	: LEDON(0) Turn on the LED and LEDOFF(1) turns off the LED
 * @return 			: nothing
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

/*
 * @description		: open device
 * @param - inode 	: inode passed to driver
 * @param - filp 	: The device file has a file structure called private_ Member variable of data
 * 					  Generally, private is used when open ing_ Data points to the device structure.
 * @return 			: 0 success; Other failures
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &dtsled; /* Set private data */
	return 0;
}

/*
 * @description		: Read data from device 
 * @param - filp 	: Device file to open (file descriptor)
 * @param - buf 	: Data buffer returned to user space
 * @param - cnt 	: Length of data to read
 * @param - offt 	: Offset relative to the first address of the file
 * @return 			: The number of bytes read. If it is negative, it indicates that the read failed
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/*
 * @description		: Write data to device 
 * @param - filp 	: A device file that represents an open file descriptor
 * @param - buf 	: Data to write to device
 * @param - cnt 	: Length of data to write
 * @param - offt 	: Offset relative to the first address of the file
 * @return 			: The number of bytes written. If it is negative, it indicates that the write failed
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* Get status value */

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* Turn on the LED */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* Turn off the LED */
	}
	return 0;
}

/*
 * @description		: Turn off / release the device
 * @param - filp 	: Device file to close (file descriptor)
 * @return 			: 0 success; Other failures
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* Device operation function */
static struct file_operations dtsled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

/*
 * @description	: Drive exit function
 * @param 		: nothing
 * @return 		: nothing
 */
static int __init led_init(void)
{
	u32 val = 0;
	int ret;
	u32 regdata[14];
	const char *str;
	struct property *proper;

	/* Gets the attribute data in the device tree */
	/* 1,Get device node: alphaled */
	dtsled.nd = of_find_node_by_path("/alphaled");

	/* Initialize LED */
	. . . . 
        
	/* Register character device driver */
	/* 1,Create device number */
	if (dtsled.major) {		/*  Defines the equipment number */
		dtsled.devid = MKDEV(dtsled.major, 0);
		register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
	} else {						/* No device number defined */
		alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);	/* Application equipment No */
		dtsled.major = MAJOR(dtsled.devid);	/* Gets the master device number of the assigned number */
		dtsled.minor = MINOR(dtsled.devid);	/* Get the secondary device number of the assigned number */
	}
	printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);	
	
	/* 2,Initialize cdev */
	dtsled.cdev.owner = THIS_MODULE;
	cdev_init(&dtsled.cdev, &dtsled_fops);
	
	/* 3,Add a cdev */
	cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);

	/* 4,Create class */
	dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
	if (IS_ERR(dtsled.class)) {
		return PTR_ERR(dtsled.class);
	}

	/* 5,Create device */
	dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
	if (IS_ERR(dtsled.device)) {
		return PTR_ERR(dtsled.device);
	}
	
	return 0;
}

/*
 * @description	: Drive exit function
 * @param 		: nothing
 * @return 		: nothing
 */
static void __exit led_exit(void)
{
	/* Unmap */
    
	/* Unregister character device driver */
	cdev_del(&dtsled.cdev);/*  Delete cdev */
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* Logout equipment number */

	device_destroy(dtsled.class, dtsled.devid);
	class_destroy(dtsled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sqy");

pinctrl and GPIO subsystems

The traditional way to configure the pin is to directly operate the corresponding registers, but this configuration method is cumbersome and prone to problems (such as pin function conflict). Pinctrl subsystem is introduced to solve this problem. The main work contents of pinctrl subsystem are as follows:

①. Obtain the pin information in the device tree.
② set the pin multiplexing function according to the obtained pin information
③ set the electrical characteristics of the pin according to the obtained pin information, such as up / down, speed, driving capacity, etc

When using, you need to add a pinctrl child node under the iomuxc node under the device tree, as shown in the following figure

&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {
        /*  LED */
        pinctrl_led: ledgrp {
            fsl,pins = <
            	MX6UL_PAD_GPIO1_IO03__GPIO1_IO03        0x10B0 /* LED0 */
            >;
        };
    ......
    };

MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 MX6UL_PAD_GPIO1_IO03 multiplexed to GPIO1_IO03, electrical attribute configuration is 0x10b0

As the name suggests, GPIO subsystem is used to initialize GPIO and provide corresponding API functions, such as setting GPIO as input and output, reading the value of GPIO, etc. The driver developer adds GPIO related information to the device tree, and then can use the API functions provided by the GPIO subsystem in the driver to operate GPIO.

As shown in the following figure, the LED device node under the root node of the device tree

gpioled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "atkalpha-gpioled";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led>;		//Add pinctrl node
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;	//gpio related information
		status = "okay";
	};

Common API functions of gpio subsystem

int gpio_request(unsigned gpio, const char *label);	//It is used to apply for a GPIO pin. GPIO must be used before using a gpio_request to apply
void gpio_free(unsigned gpio);	//If you do not use a GPIO, you can call gpio_free function to release
int gpio_direction_input(unsigned gpio);	//Used to set a GPIO as input
int gpio_direction_output(unsigned gpio, int value);	//Used to set a GPIO as output
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);

The Linux kernel provides several OF functions related to GPIO. The common OF functions are as follows:

int of_gpio_named_count(struct device_node *np, const char *propname);//It is used to obtain several GPIO information defined in an attribute of the device tree
int of_gpio_count(struct device_node *np);//And of_ GPIO_ named_ The count function is the same, but the difference is that this function counts the number of gpios of the "gpios" attribute,
int of_get_named_gpio(struct device_node *np,const char *propname,int index);	//This function obtains the GPIO number because all API functions related to GPIO in the Linux kernel use the GPIO number. This function will be similar to < &gpio5 7 GPIO in the device tree_ ACTIVE_ The attribute information of low > is converted to the corresponding GPIO number

Using pinctrl subsystem and gpio subsystem, add pinctrl node and character in the device tree, and add gpio information in the device node to obtain and initialize the node from the device tree.

	/* Set the GPIO used by BEEP */
	/* 1,Get device node: beep */
	beep.nd = of_find_node_by_path("/beep");

	/* 2, Get the gpio attribute in the device tree and get the gpio number used by BEEP */
	beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
	printk("led-gpio num = %d\r\n", beep.beep_gpio);

	/* 3,Set GPIO5_IO01 is output and outputs high level. BEEP is turned off by default */
	ret = gpio_direction_output(beep.beep_gpio, 1);

Linux interrupt

To use interrupts in bare metal machines, we need to do a lot of work, such as configuring registers, enabling IRQ and so on. The Linux kernel provides a perfect interrupt framework. We only need to apply for interrupts and register interrupt handling functions.

Linux interrupt framework

1. Add interrupt information to the device node in the device tree

interrupt-parent = <&gpio1>;	//Set the interrupt parent property value to "gpio1", that is, set the GPIO interrupt controller of the current interrupt to gpio1.
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;	//Set the interrupts attribute, that is, set the interrupt source. The 18 of the first cell represents the 18 IO of the GPIO1 group. IRQ_TYPE_EDGE_BOTH indicates that the interrupt trigger mode is edge trigger

2. Get interrupt number

Each interrupt has an interrupt number. Different interrupts can be distinguished by the interrupt number. An int variable is used in the Linux kernel to represent the interrupt number.

The interrupt information has been written into the device tree, so you can use IRQ_ of_ parse_ and_ The map function extracts the corresponding device number from the interupts attribute. Function returns the interrupt number.

If GPIO is used, int GPIO can be used_ to_ IRQ (unsigned int GPIO) function to obtain the interrupt number corresponding to GPIO.

3. Application interruption

In the Linux kernel, you need to apply to use an interrupt_ The IRQ function is used to apply for an interrupt, request_ The IRQ function may cause sleep, so you cannot use request in interrupt context or other sleep forbidden code segments_ IRQ function. request_irq function will activate (enable) interrupt, so we don't need to enable interrupt manually.

int request_irq(unsigned int irq,	//interrupt number
				irq_handler_t handler,	//Interrupt service function
				unsigned long flags,	//Interrupt flag
				const char *name,	//Interrupt name
				void *dev)	//Generally, set dev as the device structure, and dev will be passed to the interrupt handling function IRQ_ handler_ Second parameter of T

Use free_ The IRQ function releases the corresponding interrupt. free_irq deletes the interrupt handler and disables interrupts.

4. Writing interrupt handling functions

​ irqreturn_t (*irq_handler_t) (int, void *) the first parameter is the corresponding interrupt number to interrupt the processing function. The second parameter is a pointer to void, that is, a general pointer, which needs to be associated with request_ The dev parameter of the IRQ function is consistent.

Interrupt the upper and lower halves

Soft interrupt and hard interrupt

1 . Soft interrupts are generated by executing interrupt instructions, while hard interrupts are caused by peripherals.
2 . The interrupt number of hard interrupt is provided by the interrupt controller, and the interrupt number of soft interrupt is directly indicated by the instruction without using the interrupt controller.
3 . Hard interrupts are maskable, while soft interrupts are not.
4 . The hard interrupt handler should ensure that it can complete the task quickly, so that the program execution will not wait for a long time, which is called the upper half.
5 . Soft interrupt handles the unfinished work of hard interrupt. It is a mechanism of pushing back execution, which belongs to the lower half.

The Linux kernel divides interrupts into the upper half and the lower half. The main purpose is to realize the fast in and fast out of the interrupt processing function. Those time-sensitive and fast execution operations can be put into the interrupt processing function, and the upper half cannot be interrupted during processing. All the remaining work can be carried out in the lower half.

① if the content to be processed does not want to be interrupted by other interrupts, it can be placed in the top half.

② if the task to be processed is time sensitive, it can be placed in the top half.

③ if the task to be processed is related to hardware, it can be placed in the upper half
④ other tasks other than the above three points shall be given priority to the lower half

The lower half of interrupt is implemented: soft interrupt, tasklet and work queue.

Bus device driver framework

platform driver

The platform driver framework is divided into bus, device and driver. The bus is provided by the Linux kernel. When using the device tree, the device description is put into the device tree_ We don't need to write the device. We just need to implement the platform_driver.

1. Create a device node in the device tree

First, create a device node in the device tree to describe the device information. The focus is to set the value of the compatible attribute, because the platform bus needs to match the driver through the compatible attribute value of the device node!

2. When writing platform drivers, you should pay attention to compatibility attributes

When using the device tree, the platform driver will pass the of_match_table to save the compatibility value, which indicates which devices this driver is compatible with. So, of_match_table will be particularly important

3. Write platform driver

When the driver and device match successfully, the probe function will be executed. We need to execute the character device driver in the probe function. When the driver module is logged off, the remove function will execute.

An example of platform driver under the device tree is as follows

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LEDDEV_CNT 		 one 				/*  Equipment number length 	*/
#define LEDDEV_NAME 		 "dtsplatled" 	/*  Device name 	*/
#define LEDOFF 			0
#define LEDON 			1

/* leddev Equipment structure */
struct leddev_dev{
	dev_t devid;				/* Equipment number	*/
	struct cdev cdev;			/* cdev		*/
	struct class *class;		/* class 		*/
	struct device *device;		/* equipment		*/
	int major;					/* Main equipment No	*/	
	struct device_node *node;	/* LED Device node */
	int led0;					/* LED Lamp GPIO label */
};

struct leddev_dev leddev; 		/* led equipment */

/*
 * @description		: LED On / off
 * @param - sta 	: LEDON(0) Turn on the LED and LEDOFF(1) turns off the LED
 * @return 			: nothing
 */
void led0_switch(u8 sta)
{
	if (sta == LEDON )
		gpio_set_value(leddev.led0, 0);
	else if (sta == LEDOFF)
		gpio_set_value(leddev.led0, 1);	
}

/*
 * @description		: open device
 * @param - inode 	: inode passed to driver
 * @param - filp 	: The device file has a file structure called private_ Member variable of data
 * 					  Generally, private is used when open ing_ Data points to the device structure.
 * @return 			: 0 success; Other failures
 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &leddev; /* Set private data  */
	return 0;
}

/*
 * @description		: Write data to device 
 * @param - filp 	: A device file that represents an open file descriptor
 * @param - buf 	: Data to write to device
 * @param - cnt 	: Length of data to write
 * @param - offt 	: Offset relative to the first address of the file
 * @return 			: The number of bytes written. If it is negative, it indicates that the write failed
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[2];
	unsigned char ledstat;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {

		printk("kernel write failed!\r\n");
		return -EFAULT;
	}
	
	ledstat = databuf[0];
	if (ledstat == LEDON) {
		led0_switch(LEDON);
	} else if (ledstat == LEDOFF) {
		led0_switch(LEDOFF);
	}
	return 0;
}

/* Device operation function */
static struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.write = led_write,
};

/*
 * @description		: flatform Driven probe function, when driven with
 * 					  This function will be executed after the device matches
 * @param - dev 	: platform equipment
 * @return 			: 0,success; Other negative values, failed
 */
static int led_probe(struct platform_device *dev)
{	
	printk("led driver and device was matched!\r\n");
	/* 1,Set device number */
	if (leddev.major) {
		leddev.devid = MKDEV(leddev.major, 0);
		register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);
	} else {
		alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
		leddev.major = MAJOR(leddev.devid);
	}

	/* 2,Register device      */
	cdev_init(&leddev.cdev, &led_fops);
	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);

	/* 3,Create class      */
	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
	if (IS_ERR(leddev.class)) {
		return PTR_ERR(leddev.class);
	}

	/* 4,Create device */
	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
	if (IS_ERR(leddev.device)) {
		return PTR_ERR(leddev.device);
	}

	/* 5,Initialize IO */	
	leddev.node = of_find_node_by_path("/gpioled");
	if (leddev.node == NULL){
		printk("gpioled node nost find!\r\n");
		return -EINVAL;
	} 
	
	leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);
	if (leddev.led0 < 0) {
		printk("can't get led-gpio\r\n");
		return -EINVAL;
	}

	gpio_request(leddev.led0, "led0");
	gpio_direction_output(leddev.led0, 1); /* led0 IO Set to output, default high level	*/
	return 0;
}

/*
 * @description		: platform The remove function of the platform driver. This function will be executed when the platform driver is removed
 * @param - dev 	: platform equipment
 * @return 			: 0,success; Other negative values, failed
 */
static int led_remove(struct platform_device *dev)
{
	gpio_set_value(leddev.led0, 1); 	/* Turn off the LED when unloading the drive */

	cdev_del(&leddev.cdev);				/*  Delete cdev */
	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* Logout equipment number */
	device_destroy(leddev.class, leddev.devid);
	class_destroy(leddev.class);
	return 0;
}

/* Match list */
static const struct of_device_id led_of_match[] = {
	{ .compatible = "atkalpha-gpioled" },
	{ /* Sentinel */ }
};

/* platform Drive structure */
static struct platform_driver led_driver = {
	.driver		= {
		.name	= "imx6ul-led",			/* Driver name, used to match the device */
		.of_match_table	= led_of_match, /* Device tree matching table 		 */
	},
	.probe		= led_probe,
	.remove		= led_remove,
};
		
/*
 * @description	: Driver module loading function
 * @param 		: nothing
 * @return 		: nothing
 */
static int __init leddriver_init(void)
{
	return platform_driver_register(&led_driver);
}

/*
 * @description	: Driver module unloading function
 * @param 		: nothing
 * @return 		: nothing
 */
static void __exit leddriver_exit(void)
{
	platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sqy");

I2C drive

The Linux kernel divides the I2C driver into two parts:
① I2C bus driver. I2C bus driver is the I2C controller driver of SOC, also known as I2C adapter driver.
② I2C device driver. I2C device driver is a driver written for specific I2C devices.

I2C bus driver

Linux system programming

process

init process

ps -aux displays all processes running on the current system

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.7 0.1 119792 6016 ? Ss 21:35 0:06 /sbin/init

Init process is No. 1 process and is the ancestor process. The init process can be regarded as the process manager of the operating system. It is the ancestor of all other processes. The other system processes we will see are either started by the init process or by other processes started by the init process.

Program and process

A program is an ordinary file, which is a collection of instructions and data prepared to complete a specific task. These instructions and data are saved on disk in the format of "executable image".

process (process) is a specific instance of program execution. In the process of program execution, it enjoys system resources, including at least the running environment, CPU, peripherals, memory, process ID and other resources and information of the process. A program cannot be executed alone. It can be executed only after the program is loaded into memory and the system allocates resources to it. This executed program is called advance In other words, a process is an independent unit of the system for resource allocation and scheduling. Each process has its own separate address space.

Program to process

Just as we run a program (executable file), we usually enter a command in the Shell to run it. The running process includes the process of program to process conversion. The whole conversion process mainly includes the following three steps:

  1. Find the location of the program file corresponding to the command.
  2. Use the fork() function to start a new process for.
  3. In the new process, we call the exec family function to load the program files and execute the main() function in the program files.

Process state transition

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-xduvoba3-1629002288876) (C: \ users \ administrator \ appdata \ roaming \ typora \ typora user images \ image-20210808222100316. PNG)]

Create process

There are many ways to start a process in Linux. For example, you can use the system() function or the fork() function to start it

fork() function: create a child process

The fork() function is used to start a new process from an existing process. The new process is called a child process, while the original process is called a parent process.

After the fork () call in the parent process, the PID of the new child process is returned. The fork() function in the child process returns 0 after calling.

The child process is consistent with the parent process:

  • The address space of the process.
  • Process context, code snippet.
  • Process heap space, stack space, memory information.
  • Environment variable for the process.
  • Buffer for standard IO.
  • Open file descriptor.
  • Signal response function.
  • Current working path.

Content unique to child processes:

  • Process number PID. PID is the ID number and the only identifier of the process.
  • Record lock. The parent process adds a lock to a file, and the child process will not inherit the lock.
  • Pending signal. These signals are the signals that have responded but have not been processed, that is, the "suspended" signals, and the child process will not inherit these signals.

exec series functions

Using fork() function to start a child process is not very useful, because the child process is the same as the parent process. exec series functions can find the executable file according to the specified file name or directory name and use it to replace the data segment, code segment and stack segment of the original calling process. After execution, the content of the original calling process is in addition to the process number, All others are replaced by the contents of the new program.

The exec series functions directly replace the current process. When the exec series functions are called, the current process will not continue to execute. So we can start a subprocess by invoking fork(), and call the exec series function to replace the sub processes in the sub process, so that the combination of fork() and exec series functions is all that is needed to create a new process.

int execl(const char *path, const char *arg, ...)
int execlp(const char *file, const char *arg, ...)
int execle(const char *path, const char *arg, ..., char *const envp[])
int execv(const char *path, char *const argv[])
int execvp(const char *file, char *const argv[])
int execve(const char *path, char *const argv[], char *const envp[])
  • Functions with l letters in their names (execl, execlp, and execle) receive a parameter list "list" as a parameter to the caller.
  • Functions with names containing P letters (execvp and execlp) can accept a program name as a parameter. It will search and execute the program in the current execution PATH and environment variable "PATH" (i.e. relative PATH); functions with names without p letters must specify the full PATH of the program (i.e. absolute PATH is required) when calling.
  • The subroutine parameters of functions with names containing v letters (execv, execvp and execve) are loaded through an array "vector".
  • Functions with e letters in their names (execve and execle) receive more parameters indicating the list of environment variables than other functions, and can pass a string array as the environment variable of the new program through the parameter envp. The format of this envp parameter should be a string array with NULL pointer as the end tag, and each string should be expressed as The form of "environment = environments".

Terminate process

Normal termination

  • Returns from the main function.
  • Call the exit() function to terminate.
  • Call_ The exit() function terminates.

Abnormal termination

  • Call abort() function terminated abnormally.
  • Terminated by system signal.

As can be seen from the figure_ The exit() function is the simplest: directly terminate the process through system call. When terminating the process, it will clear the memory space used by the process and destroy its various data structures in the kernel; Before calling the exit system call, the exit() function checks the opening of the file and writes the contents of the file buffer back to the file, which is "clearing the I/O buffer"

Waiting process

pid_t wait(int *wstatus);

When the wait() function is called, the system will pause the execution of the parent process until a signal comes or the child process ends. If the child process has ended when the wait() function is called, the child process end status value will be returned immediately. The end status information of the child process will be returned by the parameter wsstatus. At the same time, the function will return the PID of the child process, which is usually the PID of the child process that has finished running.

pid_t waitpid(pid_t pid, int *wstatus, int options);

The function of waitpid() is the same as that of wait(), but it does not have to wait for the first terminated child process. It also has other options, such as specifying the child process waiting for a pid, providing a non blocking version of wait() function, etc. In fact, the wait() function is just a special case of the waitpid() function. When the wait function is implemented in Linux, the waitpid function is called directly.

signal

Signal (signal), also known as soft interrupt signal, is used to notify the process of an asynchronous event. It is an event generated by the Linux system in response to some conditions. It is a simulation of the interrupt mechanism at the software level. It is an asynchronous communication mode. In principle, a process receiving a signal is the same as the processor receiving an interrupt request.

Real time signal and non real time signal

Signals with signal values of 1 ~ 31 are non real-time signals, mainly because such signals do not support queuing, so the signals may be lost. For example, if the same signal is sent many times, the process can only receive it once and process it only once, so the remaining signals will be discarded. Real time signals (signals with signal values of 34 ~ 64) are different. They support queuing. The process will process as many signals as it sends to the process.

Signal acquisition correlation function

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

signal() needs to set a callback function in advance, that is, the response function that the process will jump to execute after receiving the signal

sigaction() function

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
            void     (*sa_handler)(int);//Processing function after signal acquisition
            void     (*sa_sigaction)(int, siginfo_t *, void *);//Extended signal processing function
            sigset_t   sa_mask;//Signal mask, which specifies the mask of the signal blocked during the execution of the signal processing function. The signal set in the mask is temporarily blocked during the process response signal.
            int        sa_flags;//Flag used to modify the behavior of signal processing process
            void     (*sa_restorer)(void);
        };

Transmitted signal correlation function

int kill(pid_t pid, int sig);
/*
pid The values of are as follows:
pid > 1: Send the signal sig to the process whose process ID value is pid.
pid = 0: The signal is sent to all processes in the same process group as the current process.
pid = -1: Send sig to all processes in the system, except process 1 (init).
pid < -1: Send the signal sig to each process with the process group number - pid (pid absolute value).
sig: The value of the signal to send.
Function return value:
0: Sent successfully.
-1: Sending failed.
*/

int raise(int sig);
/*
raise()Function is also a signal sending function, but different from the kill() function, the raise() function only sends signals to itself, not to other processes. It can be said that kill(getpid(),sig) is equivalent to raise(sig)
*/

unsigned int alarm(unsigned int seconds);
/*
alarm()Also known as the alarm function, it can set a timer in the process. When the time seconds specified by the timer expires, it will send a SIGALARM signal to the process.
If the alarm() function is called again within seconds to set a new alarm clock, the new setting will override the previous setting, that is, the previously set seconds will be replaced by the new alarm clock time. Its return value is the remaining seconds of the previous alarm clock. If the alarm clock is not set before, it returns 0.
*/

The Conduit

When data flows from one process connection to another, The connection between them is a pipe. We usually connect the output of one process to the input of another process through a pipe. For shell commands, the connection of commands is completed through pipe characters. Just like the "ps -aux | grep root" command, it actually executes the following process:

  • The shell is responsible for arranging the standard input and standard output of the two commands.
  • ps standard input comes from terminal mouse, keyboard, etc.
  • The standard output of ps is passed to grep as the standard input of grep.
  • Grep's standard output is connected to the terminal, that is, to the display screen. Finally, we see the output result of grep.

The pipeline is implemented as a file, but the pipeline itself does not occupy disk or other external storage space. The pipeline occupies memory space. Therefore, the pipeline on Linux is a memory buffer whose operation mode is file.

Anonymous PIPE

Anonymous pipes have the following characteristics:

  • There is no name, so you can't open it with the open() function, but you can close it with the close() function.
  • Only one-way communication (half duplex) is provided, that is, both processes can access this file. Assuming that process 1 writes to the file, process 2 can only read the contents of the file.
  • It can only be used for inter process communication with blood relationship. It is usually used for parent-child process communication.
  • Pipes communicate based on byte streams.
  • Depending on the file system, its life cycle ends with the end of the process.
  • The write operation is not atomic, so it can only be used in one-to-one simple communication.
  • The pipeline can also be regarded as a special file, and ordinary read() and write() functions can also be used for its reading and writing. However, it is not an ordinary file, does not belong to any other file system, and only exists in the memory space of the kernel, so it cannot be located by lseek().
int pipe(int pipefd[2]);//Create anonymous pipe
/*Array pipefd is used to return two file descriptors that reference the end of the pipeline. It is a pointer to an array composed of two file descriptors. pipefd[0] refers to the read end of the pipe, and pipefd[1] points to the write end of the pipe.*/

After the anonymous pipeline is created successfully, the process (parent and child processes) that created the anonymous pipeline master the read and write ends of the pipeline at the same time. However, to have data interaction between the parent and child processes, the following operations are required:

  • If you want to transfer data from the parent process to the child process, the parent process needs to close the reading end and the child process needs to close the writing end,
  • If you want to transfer data from the child process to the parent process, the parent process needs to close the write end and the child process needs to close the read end,
  • When the pipeline is not needed, close the unclosed end in the process.

Named pipe FIFO

Named pipes have the following characteristics:

  • It has a name and is stored in an ordinary file system.
  • Any process with appropriate permissions can use open() to get the file descriptor of the named pipe.
  • Just like ordinary files: use the unified read()/write() to read and write.
  • Different from ordinary files: lseek() cannot be used to locate because the data is stored in memory.
  • With write atomicity, it supports multiple writers to write at the same time, and the data will not trample on each other.
  • Following the First In First Out principle, the data first written into FIFO is read out first.
int mkfifo(const char * pathname,mode_t mode);	//Create named pipe

mkfifo() will create a special FIFO file according to the parameter pathname, and the parameter mode is the mode and permission of the file.

The FIFO file created by mkfifo() can be read and written by other processes. It can be operated by reading and writing general files, such as open, read, write, close, etc.

Mode mode and permission Parameter Description:

  • O_RDONLY: read pipeline.
  • O_WRONLY: write pipeline.
  • O_RDWR: read / write pipeline.
  • O_NONBLOCK: non blocking.
  • O_CREAT: if the file does not exist, create a new file and set permissions for it with the third parameter.
  • O_EXCL: if O is used_ An error message can be returned if the file exists at the time of creat. This parameter tests whether the file exists.

In the process of using FIFO, when a process reads the pipeline:

  • If the pipeline is blocked and there is no data in the current FIFO, it will be blocked until data is written to the read process.
  • If the pipeline is of non blocking type, the read process will immediately execute the read operation regardless of whether there is data in the FIFO. That is, if there is no data in the FIFO, the read function will immediately return 0.

In the process of using FIFO, when a process writes to the pipeline:

  • If the pipeline is of the blocking type, the write operation will be blocked until the data can be written.
  • If the pipeline is a non blocking type and cannot write all the data, the write operation will partially write or the call will fail

Message queue

Basic concepts of message queue

Message queues, shared memory, and semaphores are collectively referred to as system-V IPC

Message queuing provides a way to send a block of data from one process to another. Each data block is considered to contain a type, and the receiving process can independently receive data structures containing different types. We can avoid synchronization and blocking of named pipes by sending messages.

Comparison between message queue and signal:

  • The signal carries less information, and the message queue can carry a large amount of custom data.

Comparison between message queue and pipeline:

  • Message queues have many similarities with named pipes. Like named pipes, message queue communication processes can be unrelated processes, and they all transmit data by sending and receiving. In the named pipeline, write() is used for sending data and read() is used for receiving data. In the message queue, msgsnd() is used for sending data and msgrcv() is used for receiving data. The message queue has a maximum length limit for each data.
  • Message queues can also exist independently of sending and receiving processes. When the process terminates, the message queue and its contents will not be deleted.
  • The pipeline can only carry unformatted byte stream, and the message queue provides formatted byte stream, which can reduce the workload of developers.
  • The message queue is record oriented, in which the message has a specific format and specific priority. The receiver can selectively receive data through the message type, rather than only by default, as in the named pipeline.
  • Message queue can realize random query of messages. Messages do not have to be received in the order of first in first out, but also according to the type of message.

The implementation of message queue includes four operations: creating or opening message queue, sending message, receiving message and controlling message queue.

Message queue control API

int msgget(key_t key, int msgflg); Create or get a message queue object and return the message queue identifier.

  • Key: the key value of the message queue through which multiple processes can access the same message queue. For example, if the sending and receiving processes all use the same key value, they can use the same message queue for communication. There is a special value IPC_PRIVATE, which is used to create a private message queue for the current process.

  • msgflg: indicates the mode flag parameters of the created message queue, mainly IPC_CREAT,IPC_EXCL and permission mode,

    • If IPC_ If creat is true, it means that if there is no message queue with the same keyword and key in the kernel, a new message queue will be created; If such a message queue exists, the identifier of the message queue is returned.
    • And if it's IPC_CREAT | IPC_EXCL means that if there is no message queue with the same key value in the kernel, a new message queue will be created; If such a message queue exists, an error is reported.
    • mode refers to IPC object access permission. It uses the digital permission representation of Linux files, such as 06000666.

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); Send message function

Parameter Description:

  • msqid: message queue identifier.

  • Msgp: messages sent to the queue. Msgp can be any type of structure, but the first field must be of type long, which indicates the type of message sent, and msgrcv() function receives the message according to this. The reference format defined by msgp is as follows:

    /*msgp Defined reference format*/
    struct s_msg{
        long type;  /* Must be greater than 0, message type */
        char mtext[1];  /* Message body, which can be any other type */
    } msgp;
    
    • msgsz: the size of the message to be sent, excluding the 4 bytes occupied by the message type, that is, the length of mtext.
    • msgflg: if it is 0, it means that when the message queue is full, the msgsnd() function will block until the message can be written into the message queue; If IPC_NOWAIT means that when the message queue is full, the msgsnd() function does not wait for immediate return; If IPC_NOERROR: if the message sent is larger than size bytes, the message will be truncated, and the truncated part will be discarded without notifying the sending process.

Receive message function

/*Receive message: read the message from the message queue with the identifier msqid and store the message in msgp. After reading, delete the message from the message queue*/
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

Parameter Description:

  • msqid: message queue identifier.
  • msgp: the structure for storing messages. The structure type should be the same as that sent by the msgsnd() function.
  • msgsz: the size of the message to be received, excluding the 4 bytes occupied by the message type.
  • Msgtyp has multiple optional values: if it is 0, it means receiving the first message; if it is greater than 0, it means receiving the first message with the type equal to msgtyp; if it is less than 0, it means receiving the first message with the type equal to or less than the absolute value of msgtyp.
  • msgflg is used to set the processing method of reception,

Control message queuing function

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

Parameter Description:

  • msqid: message queue identifier.
  • cmd is used to set what operation command to use. It has multiple values:
    • IPC_STAT obtains the information of the MSG, and the obtained information will be stored in the structure msqid_ In a buf of type DS.
    • IPC_SET sets the properties of the message queue. The properties to be set must first be stored in the structure msqid_ In the DS type buf, the settable attributes include: msg_perm.uid,msg_perm.gid,msg_perm.mode and msg_qbytes, stored in the structure msqid_ds.
    • IPC_RMID immediately deletes the MSG and wakes up all processes blocked on the MSG, ignoring the third parameter.
    • IPC_INFO to obtain information about the limit value of MSG in the current system.
    • MSG_INFO to obtain the relevant resource consumption information about the MSG in the current system.
    • MSG_STAT and IPC_STAT, but msgid is the subscript of the array in which the message queue records all message queue information in the kernel. Therefore, the relevant information of all message queues in the system can be obtained by iterating all subscripts.
  • buf: related information structure buffer.

FreeRTOS

A large number of linked list structures are used in freertos. Nodes are defined as follows

struct xLIST_ITEM
{
	TickType_t xItemValue; /* Auxiliary value, used to help arrange nodes in order */ (1)
	struct xLIST_ITEM * pxNext; /* Point to the next node in the linked list */ (2)
	struct xLIST_ITEM * pxPrevious; /* Point to the previous node of the linked list */ (3)
	void * pvOwner; /* Point to the kernel object that owns the node, usually TCB */(4)
	void * pvContainer; /* It is used to point to the linked list where the node is located, usually to the root node of the linked list */ (5)
};
typedef struct xLIST_ITEM ListItem_t; /* Node data type redefinition */ (6)

The root node of the linked list is defined as follows

typedef struct xLIST
{
	UBaseType_t uxNumberOfItems; /* Linked list node counter */ (1)
	ListItem_t * pxIndex; /* Linked list node index pointer */ (2)
	MiniListItem_t xListEnd; /* The last node in the linked list */ (3)
} List_t;

struct xMINI_LIST_ITEM
{
	TickType_t xItemValue; /* Auxiliary value, used to help nodes arrange in ascending order */
	struct xLIST_ITEM * pxNext; /* Point to the next node in the linked list */
	struct xLIST_ITEM * pxPrevious; /* Point to the previous node of the linked list */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* Thin node data type redefinition */

Multitasking system

The event response of multitasking system is completed in interrupt, but the event processing is completed in task. In a multitasking system, tasks, like interrupts, also have priority, and tasks with high priority will be executed first. After an emergency event is marked, if the priority of the task corresponding to the event is high enough, it will be responded immediately.

task

In multi task system, we divide the whole system into independent and unreturnable functions according to different functions. This function is called task.

Task stack: tasks are independent and do not interfere with each other, so an independent stack space should be allocated for each task. This stack space is usually a predefined global array or a dynamically allocated memory space, but they all exist in RAM.

* * task control block TCB: * * in a multi task system, task execution is scheduled by the system. In order to schedule tasks smoothly, the system defines an additional task control block for each task. This task control block is equivalent to the ID card of the task, which contains all the information of the task, such as the stack pointer of the task, the task name, the formal parameters of the task, etc

typedef struct tskTaskControlBlock
{
	volatile StackType_t *pxTopOfStack; /* Stack top */ (1)
	ListItem_t xStateListItem; /* Task node: through this node, task control blocks can be linked to various linked lists */ (2)
	StackType_t *pxStack; /* Start address of task stack */ (3)
	/* Task name, in string form */(4)
	char pcTaskName[ configMAX_TASK_NAME_LEN ];
} tskTCB;

* * task creation method: * * in FreeRTOS, there are two methods for creating tasks, one is dynamic creation, the other is static creation. During dynamic creation, the memory of task control block and stack is dynamically allocated during task creation. When a task is deleted, the memory can be released. During static creation, the memory of task control block and stack needs to be defined in advance. It is static memory. When a task is deleted, the memory cannot be released

Ready list: List_t pxReadyTasksLists[ configMAX_PRIORITIES ];

The ready list is actually a list_ The size of the array is determined by the configmax that determines the maximum task priority_ Priorities determines that 256 priorities are supported at most. The subscript of the array corresponds to the priority of tasks. Tasks with the same priority are uniformly inserted into the same linked list of the ready list.

Protection of critical section

PendSV interrupt: PendSV is a suspended exception. If we configure it to the lowest priority, if multiple exceptions are triggered at the same time, it will be executed after the execution of other exceptions, and any exception can interrupt it.

In FreeRTOS, system scheduling finally generates PendSV interrupt, and task switching is realized in PendSV Handler, so it can still be attributed to interrupt. In this case, the protection of critical section by FreeRTOS finally returns to the control of interrupt on and off

Cortex-M kernel fast shutdown interrupt instruction

CPSID I ;//Set PRIMASK=1; Off interrupt
CPSIE I ;//Set PRIMASK=0; Open interrupt
CPSID F ;//Set FAULTMASK=1; Guan anomaly
CPSIE F ;//Set FAULTMASK=0; Abnormal opening

Cortex-M kernel interrupt mask registers are as follows

Register nameFunction description
PRIMASKThis is a single bit register. After it is set to 1, all maskable exceptions are turned off, leaving only NMI and hard FAULT to respond. Its default value is 0, indicating that there is no shutdown interrupt.
FAULTMASKWhen it is set to 1, only NMI can respond, and all other exceptions, even hard FAULT, are closed. Its default value is also 0, indicating that there is no off exception.
BASEPRIThis register has a maximum of 9 bits (determined by the number of bits expressing priority). It defines the threshold of masked priority. When it is set to a certain value, all interrupts with priority number greater than or equal to this value will be turned off (the higher the priority number, the lower the priority). However, if it is set to 0, no interrupt will be turned off, and 0 is also the default value.

In FreeRTOS, the on and off of interrupts are realized by operating the BASEPRI register, that is, interrupts greater than or equal to the value of BASEPRI will be masked, and interrupts less than the value of BASEPRI will not be masked and will not be managed by FreeRTOS.

Guanzhong break function

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
void vPortRaiseBASEPRI( void )
{
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; 
	__asm
	{
		msr basepri, ulNewBASEPRI 
		dsb
		isb
	}
}
/* The off interrupt function with return value can be nested and can be used in the interrupt */ 
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
ulPortRaiseBASEPRI( void )
{
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; 
	__asm
	{
		mrs ulReturn, basepri 
		msr basepri, ulNewBASEPRI 
		dsb
		isb
	}
	return ulReturn; 
}

Interrupt function

/* Interrupt function without interrupt protection */
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) (2)
/* Interrupt function with interrupt protection */
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x) (3)
void vPortSetBASEPRI( uint32_t ulBASEPRI ) (1)
{
	__asm
	{
		msr basepri, ulBASEPRI
	}
}

Application of critical segment code

/* In case of interruption, critical segments can be nested */
{
	uint32_t ulReturn;
	/* Enter the critical segment, which can be nested */
	ulReturn = taskENTER_CRITICAL_FROM_ISR();
	/* Critical segment code */
	/* Exit critical section */
	taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

/* In the case of non interruption, critical segments cannot be nested */
{
	/* Enter critical section */
	taskENTER_CRITICAL();
	/* Critical segment code */
	/* Exit critical section*/
	taskEXIT_CRITICAL();
}

Topics: Linux ARM