Kernel memory management, dynamic allocation and IO access, LED driver

1. Kernel memory management framework

The kernel divides the physical memory into N blocks of 4KB, which are called pages. Each page is represented by a struct page and is maintained using a partnership algorithm.

Kernel address space division diagram:

3G~3G + 896M: low-end memory, direct mapping virtual address = 3G + physical address

? Subdivided into: ZONE_DMA, ZONE_NORMAL

? Allocation:

 1. kmalloc: small memory allocation, slab algorithm
2. get_free_page: Full page allocation, 2 to the nth power page, n is a maximum of 10

Greater than 3G + 896M: high-end memory

? Subdivided into: vmalloc area, persistent mapping area, fixed mapping area

? Allocation method: vmalloc: virtual addresses are continuous, physical addresses are not continuous

?

2. Commonly used dynamic allocation in the kernel

2.1 kmalloc

? Function prototype:

void *kmalloc(size_t size, gfp_t flags);

The memory requested by kmalloc() is located in the direct mapping area and is physically continuous. They have only a fixed offset from the real physical address. Because there is a relatively simple conversion relationship, there is a limit on the size of the applied memory and cannot More than 128KB.
?
More commonly used flags (methods of allocating memory):

  • GFP_ATOMIC – The process of allocating memory is an atomic process, and the process of allocating memory will not be interrupted by (high-priority processes or interrupts);
  • GFP_KERNEL – allocate memory normally;
  • GFP_DMA – To allocate memory to the DMA controller, you need to use this flag (DMA requires that the virtual address and physical address be allocated consecutively).

Reference usage of flags:
 |– Process context, can sleep    GFP_KERNEL
 |– Exception context, cannot sleep    GFP_ATOMIC
 | |–Interrupt handler   GFP_ATOMIC
   |–Softirq     GFP_ATOMIC
   |– Tasklet     GFP_ATOMIC
|– Memory used for DMA, can sleep | GFP_DMA | GFP_KERNEL
|– Memory used for DMA, cannot sleep GFP_DMA |GFP_ATOMIC
?
The corresponding memory release function is:

void kfree(const void *objp);
void *kzalloc(size_t size, gfp_t flags)

2.2 vmalloc

void *vmalloc(unsigned long size);

The vmalloc() function will give a continuous memory area in the virtual memory space, but this continuous virtual memory is not necessarily continuous in the physical memory. Since vmalloc() does not guarantee that it will apply for continuous physical memory, there is no limit on the memory size applied for. If you need to apply for a larger memory space, you need to use this function.

The corresponding memory release function is:

void vfree(const void *addr);

Note: vmalloc() and vfree() can sleep and therefore cannot be called from exception context.

2.3 Comparison of kmalloc & vmalloc

The common features of kmalloc(), kzalloc(), and vmalloc() are:

  1. Memory used to apply for kernel space;
  2. Memory is allocated in bytes;
  3. The allocated memory virtual addresses are contiguous;

The differences between kmalloc(), kzalloc(), and vmalloc() are:

  1. kzalloc is a kmalloc operation that forces clearing; (the following description does not distinguish between kmalloc and kzalloc)
  2. The memory size allocated by kmalloc is limited (128KB), but vmalloc has no limit;
  3. kmalloc can guarantee that the allocated memory physical address is continuous, but vmalloc cannot guarantee;
  4. The process of kmalloc allocating memory can be an atomic process (using GFP_ATOMIC), while vmalloc may cause blocking when allocating memory;
  5. The overhead of kmalloc allocating memory is small, so kmalloc is faster than vmalloc;

Under normal circumstances, memory only needs to be physically contiguous when it is to be accessed by DMA. However, for performance reasons, kmalloc() is generally used in the kernel, and vmalloc() is used only when a large block of memory needs to be obtained.

2.4 Allocation selection principles:

  1. Use kmalloc for small memory (< 128k), vmalloc or get_free_page for large memory
  2. If you need a relatively large memory and require higher usage efficiency, use get_free_page, otherwise use vmalloc

Example of applying for memory:

int __init mychar_init(void)
{<!-- -->
int ret = 0;
dev_t devno = MKDEV(major, minor);

/* Manually apply for device number */
ret = register_chrdev_region(devno, char_num, "mychar");
if (ret) {<!-- -->
/* Dynamically apply for device number */
ret = alloc_chrdev_region( & amp;devno, minor, char_num, "mychar");
if(ret){<!-- -->
printk("get devno failed\\
");
return -1;
}
/*Application successful, update device number*/
major = MAJOR(devno);
}
\t
/* Apply for dynamic memory kmalloc */
pgmydev = (struct mychar_dev *)kmalloc(sizeof(struct mychar_dev), GFP_KERNEL);
if(NULL == pgmydev) {<!-- -->
unregister_chrdev_region(devno, char_num);
printk("kmalloc for 'struct mychar_dev' failed\\
");
return -1;
}
\t
/* Specify the set of operating functions for the struct cdev object */
cdev_init( & amp;pgmydev->mydev, & amp;myops);

/* Add the struct cdev object to the corresponding data structure of the kernel */
pgmydev->mydev.owner = THIS_MODULE;
cdev_add( & amp;pgmydev->mydev, devno, char_num);
\t
return 0;
}

3. IO access——-Access the registers of the peripheral controller

Two ways:

  1. IO port: accessed using IO instructions on X86
  2. IO memory: Peripheral registers have corresponding physical addresses in the SOC chip manual

IO memory access interface:

static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
/*
Function: Implement mapping of IO pins
Parameters: offset: offset address of the pin
Size: The size of the pin mapping space
Return value: Returns the mapped virtual address on success, NULL on failure
*/

static inline void iounmap(volatile void __iomem *addr)
/*
Function: Unmap io pins
Parameters: addr: address of io pin mapping
*/

unsigned readb(void *addr);//1 byte or ioread8(void *addr)
unsigned readw(void *addr);//2 bytes or ioread16(void *addr)
unsigned readl(void *addr);//4 bytes or ioread32(void *addr)
/*
Function: Read the value of the register
Parameters: addr address
Return value: read data
*/

void writeb(unsigned value, void *addr);//1 byte or iowrite8(u8 value, void *addr)
void writew(unsigned value, void *addr);//2 bytes or iowrite16(u16 value, void *addr)
void writel(unsigned value, void *addr);//4 bytes or iowrite32(u32 value, void *addr)
/*
 Function: Write data to the specified register.
 Parameters: value: data to be written to the register
Address: virtual address of the register
*/

4. led driver

  1. Read schematic

  2. Check the SOC chip manual

    GPX2_7 led2 GPX2CON—-0x11000C40-28~31—–0001 GPX2DAT—–0x11000C44—–7

    GPX1_0 led3 GPX1CON—-0x11000C20-0~3—–0001 GPX1DAT—-0x11000C24—–0

    GPF3_4 led4 GPF3CON—-0x114001E0-16~19—–0001 GPF3DAT—-0x114001E4—–4

    GPF3_5 led5 GPF3CON—-0x114001E0-20~23—–0001 GPF3DAT—-0x114001E4—–5

  3. Write driver

    a. Design device data type

    struct myled_dev
    {<!-- -->
    struct cdev mydev;
        
        unsigned long * led2con;
        unsigned long * led2dat;
    
        unsigned long * led3con;
        unsigned long * led3dat;
        
        unsigned long * led4con;
        unsigned long * led4dat;
    
        unsigned long * led5con;
        unsigned long * led5dat;
    };
    

    b. Consider the functions that need to be supported

    c. Module entry: ioremap + set to output

    d. Module export: iounmap

    e. Write the light-off function and the light-on function to implement ioctl

Example:
mychar_led.h

#ifndef MYCHAR_LED_H
#define MYCHAR_LED_H


#include <asm/ioctl.h>

#define MYCHAR_LED_MAGIC 'c'

#define MYCHAR_LED_OFF _IO(MYCHAR_LED_MAGIC, 0)
#define MYCHAR_LED_ON _IO(MYCHAR_LED_MAGIC, 1)


#endif

mychar_led.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>

#include "mychar_led.h"

#define BUF_LEN 100

#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24

#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44

#define GPF3CON 0x114001E0
#define GPF3DAT 0x114001E4


int major = 11; //major device number
int minor = 0; //Minor device number
int char_num = 1; //Number of device numbers

struct myled_dev
{<!-- -->
struct cdev mydev;

/* Store the virtual address of the mapped pin */
volatile unsigned long * pled2_con;
volatile unsigned long * pled2_dat;

volatile unsigned long * pled3_con;
volatile unsigned long * pled3_dat;

volatile unsigned long * pled4_con;
volatile unsigned long * pled4_dat;

volatile unsigned long * pled5_con;
volatile unsigned long * pled5_dat;

};
struct myled_dev *pgmydev = NULL;

int myled_open (struct inode *pnode, struct file *pfile)//Open device
{<!-- -->
pfile->private_data = (void *) (container_of(pnode->i_cdev, struct myled_dev, mydev));
return 0;
}

int myled_close(struct inode *pnode, struct file *pfile)//Close the device
{<!-- -->
return 0;
}

void led_on(struct myled_dev *pmydev, int ledno) {<!-- -->
switch(ledno) {<!-- -->
case 2:
writel(readl(pmydev->pled2_dat) | (0x1 << 7), pmydev->pled2_dat);
break;
case 3:
writel(readl(pmydev->pled3_dat) | (0x1 << 0), pmydev->pled3_dat);
break;
case 4:
writel(readl(pmydev->pled4_dat) | (0x1 << 4), pmydev->pled4_dat);
break;
case 5:
writel(readl(pmydev->pled5_dat) | (0x1 << 5), pmydev->pled5_dat);
break;
}
}

void led_off(struct myled_dev *pmydev, int ledno) {<!-- -->
switch(ledno) {<!-- -->
case 2:
writel(readl(pmydev->pled2_dat) & amp; (~(0x1 << 7)), pmydev->pled2_dat);
break;
case 3:
writel(readl(pmydev->pled3_dat) & amp; (~(0x1 << 0)), pmydev->pled3_dat);
break;
case 4:
writel(readl(pmydev->pled4_dat) & amp; (~(0x1 << 4)), pmydev->pled4_dat);
break;
case 5:
writel(readl(pmydev->pled5_dat) & amp; (~(0x1 << 5)), pmydev->pled5_dat);
break;
}
}

long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{<!-- -->
struct myled_dev *pmydev = (struct myled_dev *)pfile->private_data;

if(arg < 2 || arg > 5) {<!-- -->
return -1;
}

switch(cmd) {<!-- -->
case MYCHAR_LED_OFF:
led_off(pmydev, arg); //Turn off the light
break;
case MYCHAR_LED_ON:
led_on(pmydev, arg); //Turn on the light
break;
default:
return -1;
}

return 0;
}


struct file_operations myops = {<!-- -->
.owner = THIS_MODULE,
.open = myled_open,
.release = myled_close,
.unlocked_ioctl = myled_ioctl,
};

void ioremap_ledreg(struct myled_dev *pmydev) {<!-- -->
\t
pmydev->pled2_con = ioremap(GPX2CON, 4);
pmydev->pled2_dat = ioremap(GPX2DAT, 4);

pmydev->pled3_con = ioremap(GPX1CON, 4);
pmydev->pled3_dat = ioremap(GPX1DAT, 4);

pmydev->pled4_con = ioremap(GPF3CON, 4);
pmydev->pled4_dat = ioremap(GPF3DAT, 4);

pmydev->pled5_con = pmydev->pled4_con;
pmydev->pled5_dat = pmydev->pled4_dat;

}

void set_output_ledconreg(struct myled_dev *pmydev) {<!-- -->

/* Set the (GPX2_7...) pin to the output function by setting the (GPX2_CON...) register */
writel((readl(pmydev->pled2_con) & amp; (~(0xF << 28))) | (0x1 << 28), pmydev->pled2_con);
writel((readl(pmydev->pled3_con) & amp; (~(0xF << 0))) | (0x1), pmydev->pled3_con);
writel((readl(pmydev->pled4_con) & amp; (~(0xF << 16))) | (0x1 << 16), pmydev->pled4_con);
writel((readl(pmydev->pled5_con) & amp; (~(0xF << 20))) | (0x1 << 20), pmydev->pled5_con);
\t
/* Write the specified value to the (GPX2_DAT...) register to turn off LED2 LED3 LED4 LED5 */
writel(readl(pmydev->pled2_dat) & amp; (~(0x1 << 7)), pmydev->pled2_dat);
writel(readl(pmydev->pled3_dat) & amp; (~(0x1 << 0)), pmydev->pled3_dat);
writel(readl(pmydev->pled4_dat) & amp; (~(0x1 << 4)), pmydev->pled4_dat);
writel(readl(pmydev->pled5_dat) & amp; (~(0x1 << 5)), pmydev->pled5_dat);
}

void iounmap_ledreg(struct myled_dev *pmydev) {<!-- -->

iounmap(pmydev->pled2_con);
pmydev->pled2_con = NULL;
iounmap(pmydev->pled2_dat);
pmydev->pled2_dat = NULL;

iounmap(pmydev->pled3_con);
pmydev->pled3_con = NULL;
iounmap(pmydev->pled3_dat);
pmydev->pled3_dat = NULL;
\t
iounmap(pmydev->pled4_con);
pmydev->pled4_con = NULL;
iounmap(pmydev->pled4_dat);
pmydev->pled4_dat = NULL;

pmydev->pled5_con = NULL;
pmydev->pled5_dat = NULL;
}

int __init myled_init(void)
{<!-- -->
int ret = 0;
dev_t devno = MKDEV(major, minor);

/* Manually apply for device number */
ret = register_chrdev_region(devno, char_num, "myled");
if (ret) {<!-- -->
/* Dynamically apply for device number */
ret = alloc_chrdev_region( & amp;devno, minor, char_num, "myled");
if(ret){<!-- -->
printk("get devno failed\\
");
return -1;
}
/*Application successful, update device number*/
major = MAJOR(devno);
}

pgmydev = (struct myled_dev *)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
if(NULL == pgmydev) {<!-- -->
unregister_chrdev_region(devno, char_num);
printk("kmalloc for 'struct myled_dev' failed\\
");
return -1;
}

memset(pgmydev, 0, sizeof(struct myled_dev));
\t
/* Specify the set of operating functions for the struct cdev object */
cdev_init( & amp;pgmydev->mydev, & amp;myops);

/* Add the struct cdev object to the corresponding data structure of the kernel */
pgmydev->mydev.owner = THIS_MODULE;
cdev_add( & amp;pgmydev->mydev, devno, char_num);

/* ioremap */
ioremap_ledreg(pgmydev);

/* con-register set output */
set_output_ledconreg(pgmydev);

return 0;
}

void __exit myled_exit(void)
{<!-- -->

dev_t devno = MKDEV(major, minor);
\t
/* iounmap */
iounmap_ledreg(pgmydev);

/* Remove a character device from the kernel */
cdev_del( & amp;pgmydev->mydev);

/* Recycling device number */
unregister_chrdev_region(devno, char_num);

/* Release memory */
kfree(pgmydev);
pgmydev = NULL;
}

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

tested_app.c

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

#include "mychar_led.h"

int main(int argc, char *argv[])
{<!-- -->
int fd = -1;
int onoff = 0; //1 -- turn on the light 0 -- turn off the light
int no = 0; //2 -- LED2 3 -- LED3 ……

if(argc < 4) {<!-- -->
printf("The argument is too failed\\
");
return 1;
}

sscanf(argv[2], "%d", & amp;onoff);
sscanf(argv[3], "%d", & amp;no);

if(no < 2 || no > 5) {<!-- -->
printf("led-no is invlide\\
");
return 2;
}

fd = open(argv[1], O_RDONLY);
if(fd < 0) {<!-- -->
printf("open is failed\\
");
return 3;
}

if(onoff) {<!-- -->
ioctl(fd, MYCHAR_LED_ON, no);
} else {<!-- -->
ioctl(fd, MYCHAR_LED_OFF, no);
}

return 0;
}