3 character device driver programming

catalogue

1 miscellaneous character device

Core structure

Operation function

Miscellaneous character device registration application

2 early classic registration

Operation function

Classic device registration application

3 Linux2.6 character device

Core structure

Operation function

Linux2.6 device registration application

Linux system draws lessons from the idea of object-oriented to manage device drivers. Each type of device will define a specific structure to describe it. This structure contains the basic information of the device and the method of operating the device (function pointer). Therefore, writing a program is actually to implement the core structure, and then register this structure into the kernel.

The driver is to control the hardware downward and provide the interface upward. The interface provided upward here finally corresponds to the application layer in three ways: device file, / proc, / sys. The most commonly used is to use device file, which has three forms:

  1. Character device: a device that is accessed sequentially in bytes. (such as serial port, RTC, LCD screen...)
  2. Block device: a device that reads and writes in blocks. (emmc, flash, sd and other storage devices)
  3. Network equipment: for sending and receiving data packets. (wired / wireless network card)

The most commonly used Linux devices are character devices. In the current kernel version, there are three popular character device programming modules:

1. Miscellaneous equipment drive model

2. Early classic standard character device driver model

        3,Linux2.6 standard character device driver model

Note: installing miscellaneous devices will automatically generate device files in the / dev directory, while the early classic character device driver model and Linux 2 6 the standard character device driver model will not generate device files by itself

1 miscellaneous character device

Before learning miscellaneous character equipment, first understand the concept of equipment number, which is divided into primary equipment number and secondary equipment number. Main equipment number: it is used to identify class I equipment (similar to class number); Secondary equipment number: it is used to identify the equipment in a class of equipment (student number of students in the class).

Miscellaneous character devices mainly learn one structure and two functions.

Core structure

Parameter miscdevice header file path: #include < Linux / miscdevice h>

struct miscdevice  {
	int minor;         //Secondary equipment No
	const char *name;  //Device name, / dev /
	  const struct file_operations *fops;//File operation method set pointer (linux-3.2.2\include\linux\ fs.h)
	//The following is the kernel usage, which users do not need to pay attention to
	struct list_head list;
	struct device *parent;
	struct device *this_device;
	const char *nodename;
	mode_t mode;
};

Operation function

int misc_register(struct miscdevice *misc); //Register function
//Function: register a miscellaneous character device with the kernel
//Parameter: misc has implemented the address of struct miscdevice structure variable of minor,name,fop and three members
//Return: 0: Registration succeeded, < 0: failed, return failure error code
int misc_deregister(struct miscdevice *misc);//Logoff function
//Function: unregister a miscellaneous character device that has been registered in the kernel
//Parameter: the address of the struct miscdevice structure variable that misc has registered
//Return: 0: Registration succeeded, < 0: failed, return failure error code

Miscellaneous character device registration application

Write 3 files, 1 is the kernel layer driver file hello c. 2 is the user layer file app c. 3 is Makefile file, which realizes the function of LED flashing. Note: call misc once_ The register registration function will only occupy one secondary device number. The primary device number of miscellaneous devices is fixed to 10, and the change of secondary device number is returned to 0-254. Writing 255 represents the automatic allocation of secondary device number. The device file is automatically generated.

1 kernel layer: Hello c

//**************Kernel layer code hello c**************
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
unsigned int  *base = NULL;
//Base address of LED lamp control register
#define GPM4CON 	*((volatile unsigned int *)(base))    
//LED data register base address
#define GPM4DAT 	*((volatile unsigned int *)(base +1))
int led_open (struct inode *inode, struct file *fp)
{
	printk("hello open is ok\n");
	GPM4DAT &=~(0XF<<0); //Turn on the LED
	return 0;
}

int  led_close (struct inode *inode, struct file *fp)
{
	printk("hello close is ok\n");
	GPM4DAT |=(0XF<<0); //Turn off the small lights
	return 0;
}
struct file_operations hello_fops = {//Collection of file operation methods
	.owner = THIS_MODULE,/*Module reference, which is assigned at any time_ MODULE */ 
	.open = led_open,
	.release = led_close,
};
//Initialize miscellaneous character device control structure
struct miscdevice  misc = {
	.minor = 255,/*It can be specified directly. Generally, it is 255, indicating that it is automatically allocated by the system*/
	.name = "led",
	.fops = &hello_fops,
};
static int __init led_init(void)
{
	int ret;
	ret = misc_register(&misc);//Register device
	if(ret <0)
	{
		printk("misc register error!\n");
		return -1;
	}
	printk("misc register sucess!\n");
    //Address mapping
	base =(unsigned int  *) ioremap(0x110002e0, 0x16);
	GPM4CON &=~(0XFFFF<<0);//configure port
	GPM4CON |= (0X1111<<0);
	GPM4DAT |= (0XF<<0);//LED off by default
	return 0;
}
static void __exit led_exit(void)
{
	iounmap(base);//Unmap address
	misc_deregister(&misc);//Logout device
	printk("misc register exit\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

2. User layer: app c

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int fd;
int main()
{
	while(1)
	{
		fd=open("/dev/led",O_RDWR);
		sleep(2);
		close(fd);
		sleep(2);
	}
	return 0;
}

3. Prepare Makefile file

obj-m += led.o
KDIR:=/root/work/linux-3.5 
all:
	make -C $(KDIR) M=$(PWD) modules
	arm-linux-gcc -o app app.c
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order app

4 load the module in the super terminal of the development board and run the user program

insmod led.ko   //Load LED module
lsmod           //View mounted modules
./app           //Run user program
//Here the LED flashes

2 early classic registration

Earlier versions of device registration used the function register_chrdev(), the operation is successful, and the return value is 0. When closing the device, you usually need to unregister the original device and use the function unregister_chrdev(). The primary equipment number and secondary equipment number cannot be greater than 255. The core structure of miscellaneous equipment does not have a core structure;

Equipment number: primary equipment number: 0 ~ 255 (10 is for miscellaneous equipment). Secondary equipment number: 0 ~ 255. When 255 is passed, it means that the secondary equipment number is automatically assigned. Feature: after installation, the / dev / device file node will not be created automatically, but needs to be created manually with mknod command.

mknod /dev/xxx c primary device number secondary device number

Call a register_ After chrdev is registered, 256 secondary device numbers are occupied. That is, a master device number can only use register_ The chrdev function is registered once.

Operation function

Header file: #include < Linux / Fs h>

int register_chrdev(unsigned int major,const char *name,const struct file_operations *fops);
/*Function: register an early classic standard character device
 Parameter: major master device number, 0 ~ 255 (except 10). The master device number registered by this kind of driver cannot be the same as that of other drivers, otherwise it will fail. Therefore, when you are not sure which number is OK, it can only be automatically allocated by the kernel. When major passes 0, it means that the kernel will automatically allocate an available master device number.
name:The device name does not need to be the same as the corresponding node name under / dev. after registration, this name will appear in the / proc/device file. (you can view it with cat /proc/device).
fops:File operation method pointer
 Return value: when major =0: Success: return the assigned primary device number; failure: return a negative number.
		When major > 0: Success: return 0 				   Failed: negative number returned.*/
void unregister_chrdev(unsigned int major,const char *name );
/*Function: log off an existing standard character device
 Parameter: major: main device number: Name: device name, using register_chrdev registered device name
 Return: None*/

Classic device registration application

1. Kernel layer: register_chrdev.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
static int major = 0;
int hello_open(struct inode *inode, struct file *fp)
{
	printk("hello_open ok\n");
	return 0;
}
ssize_t hello_read(struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
	printk("hello_read ok\n");
	return 0;
}
ssize_t hello_write(struct file *fp, const char __user *buf, size_t size, loff_t *offset)
{
	printk("hello_write ok\n");
	return 0;
}
int hello_close(struct inode *inode, struct file *fp)
{
	printk("hello_close ok\n");
	return 0;

}
struct file_operations my_fops = 
 {
	.owner=THIS_MODULE,
	.open = hello_open,
	.read = hello_read,
	.write = hello_write,
	.release = hello_close,
}; 
static int __init hello_register_init(void)
{
	
	major = register_chrdev(0, "hello_chrdev",&my_fops);
	if(major < 0)
	{
			printk("register chrdev error\n");
			return -1;
	}
	printk("register chrdev ok\n");
	printk("major = %d\n",major);
	
	return 0;
}
static void __exit hello_registet_exit(void)
{
	 unregister_chrdev(major,"hello_chrdev");
}
module_init(hello_register_init);
module_exit(hello_registet_exit);
MODULE_LICENSE("GPL");

2. User layer: app c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
	int fp;
	fp =open(argv[1], O_RDWR);
	if(fp < 0)
	{
		printf("open error\n");
		return -1;
	}
	write(fp,NULL,0);
	read(fp,NULL,0);
	close(fp);
	return 0;
}

3 Makefile file

obj-m += register_chrdev.o
KDIR:=/root/work/linux-3.5 
all:
	make -C $(KDIR) M=$(PWD) modules
	arm-linux-gcc -o app app.c
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order app

4. Manually create the device node file, load the module and run the user program

//Execute in the terminal serial port of the development board
mknod /dev/led c 250 0  //Create device node
insmod led.ko           //Loading module
./app led               //Run user program

3 Linux2.6 character device

Using dev_t represents a device number, dev_t is actually a u32 type. The upper 12 digits are the main equipment number and the lower 20 digits are the secondary equipment number. Main equipment No.: dev_t high 12 digits, 2^12=4K (10 is for miscellaneous equipment), secondary equipment No.: dev_t lower 20 bits, 2^20=1M

Core structure

//Linux2.6. Core structure of standard character equipment: cdev
#include <linux/cdev.h>
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops; //Operation method of equipment file
	struct list_head list;
	dev_t dev;                     //32-bit device number: including primary and secondary
	unsigned int count;			  //How many connected equipment numbers are used
};
//Members that must be implemented:
//	ops: file operation method pointer
//	Dev: starting device number (including primary device number and secondary device number), dev_t is actually a u32 type
//	count: the number of secondary device numbers to be occupied by this device (continuous), starting from the secondary device number in dev.

Note: after installation, the / dev / device file node will not be created automatically, but needs to be created manually with mkond command
mknod /dev/xxx c primary device number secondary device number, call CDEV once_ After add is registered, the specified number of secondary numbers is occupied. The number can be specified by yourself. A master device can use CDEV_ The add function is registered more than once

Synthetic equipment No.: MKDEV(ma,mi):ma: main equipment No; mi: secondary equipment number

Decompose equipment number: MAJOR(dev): decompose the main equipment number from the equipment number dev

MINOR(dev): decompose the secondary device number from the device number dev

Operation function

Including functions related to automatic creation of equipment nodes.

//1. Static device number application function header file:
#include <linux/fs.h>
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//Function: apply for an equipment number range
//Parameter: from: starting equipment number (primary and secondary); count: number of consecutive secondary equipment numbers;
//Name: device name, which does not need to be the same as the device file name of / dev /
//Return value: 0: success; Failed: negative number returned

//1. Dynamic device number application function header file:
#include <linux/fs.h>
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
//Function: apply for an equipment number range
//Parameter: dev: store the first assigned device (including primary and secondary device numbers); baseminor: to assign the starting secondary equipment number; count: number of consecutive secondary equipment numbers; Name: device name, which does not need to be the same as the device file name of / dev /
//Return value: 0: success; Failed: negative number returned
 
//2. Device number release function: header file:
#include <linux/fs.h>
void unregister_chrdev_region(dev_t from,unsigned int count);
//Function: release a device number range
//Parameter: from: starting equipment number (primary and secondary) (including primary and secondary equipment numbers); count: number of consecutive secondary equipment numbers
//Return value: None

//3. Core structure allocation function: header file:
#include <linux/cdev.h>
struct cdev *cdev_alloc(void);
//Function: allocate a core structure in the heap space. Note that when not in use, release it with kfree function;
//Parameter: None
//Return value: returns the first address allocated to the struct cdev structure space 
//Note: remember to release after use, otherwise it will cause memory leakage.

//4. Core structure initialization function: header file:
#include <linux/cdev.h>
void cdev_init(struct cdev *cdev,const struct file_operations *fops);
//Function: initialize the core structure, specifically clearing the core structure and initializing the list, kobj and OPS members of the core structure
//Parameter: cdev: pointer to the core structure to be initialized; fops: file operation method structure pointer
//Return value: None
//Note: when writing this driving model, it is not necessary to define the struct cdev structure variable initialization, because CDEV is called_ The init function will clear it to 0 when it is defined, and the initial is invalid when it is defined

//5. Device registration function: header file:
#include <linux/cdev.h>
int cdev_add(struct cdev *p,dev_t dev,unsigned count);
//Function: register a cdev structure
//Parameter: p: initialized core structure pointer; dev: starting equipment number (including primary and secondary equipment numbers);
//count: number of consecutive equipment numbers
//Return value: Success: 0; Failed: negative number returned

//6. Device logoff function: header file:
#include <linux/cdev.h>
void cdev_del(struct cdev *p);
//Function: log off a cdev structure
//Parameter: p: struct cdev structure pointer registered earlier
//Value: None

//**********Automatically create device node related functions**********
//1. Automatic node function creation: header file 
#include<linux/device.h>
struct device *device_create(
struct class *cls, // The class pointer of the device is managed by that class
struct device *parent, // NULL
dev_t devt,      // The combination of primary equipment number and secondary equipment number is 32 bits, primary 12 bits and secondary 20 bits
void *drvdata,   // NULL, device private data
const char *fmt, ... /*Node name under fmt:/dev / that can be formatted*/
);

//2. Create class: header file
#include<linux/device.h>
class_create(ower,name);
//This function returns a pointer  
//Owner: the owner of the class. The fixed is THIS_MODULE
//nameļ¼› Class name, whatever, can have meaning. It's best not to be the name of the device under / dev /. This name determines / sys/class/name. When a class is created, a directory named name will be generated in the / sys/class / directory

Linux2.6 device registration application

Kernel layer: linux26_class.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/io.h>

static int major = 250;
static int minor = 0;
static dev_t devno;
static  struct cdev cdev;
static struct class *cls;
static struct device *tst_device;

static int hello_open(struct inode *inode, struct file *file)//open
{
	printk("hello_open\n");
	return 0;
}
static int hello_release(struct inode *inode, struct file *file)//close
{
	printk("hello_release\n");
	return 0;
}

struct file_operations hello_ops ={
	.owner		= THIS_MODULE,
	.open		= hello_open,        //open();
	.release	       = hello_release,    //close();
};

static int hello_init(void)//initialization
{
	int ret;
	devno = MKDEV(major,minor);
	ret = register_chrdev_region(devno, 1, "hello");//Static application
	if(ret<0)
	{
		
		if(alloc_chrdev_region(&devno, 0, 1, "hello"))//Dynamic application
		{
			printk("fail \n");
			return ret;
		}
	}
	cls = class_create(THIS_MODULE, "mycls");
	tst_device = device_create(cls, NULL, devno, NULL, "my_device");
	printk("hello_init()\n");
	printk("major = %d \n",MAJOR(devno));
	
	cdev_init(&cdev, &hello_ops);//initialization
	ret = cdev_add(&cdev, devno, 1);//register
	if(ret<0)
	{
		unregister_chrdev_region(devno, 1);//Logout equipment number
		return ret;
	}
	return 0;
}
static void hello_exit(void)
{
	printk("hello_exit()\n");
	device_destroy(cls, devno);
	class_destroy(cls);
	cdev_del(&cdev);//Unregister cdev structure space
	unregister_chrdev_region(devno,1);//Release device number
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

User layer: test c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
	int fd;
	int ret;
	int red;
	char buf[1024]={0};
	fd = open("/dev/my_device",O_RDWR);
	if(fd < 0)
	{
		printf("open error\n");
		return -1;
	}
#if 0
	ret = read(fd,buf,sizeof(buf));
	if(ret < 0)
	{
		printf("read error\n");
		return -1;
	}
	red = write(fd,buf,sizeof(buf));
	if(red < 0)
	{
		printf("write error\n");
		return -1;
	}
#endif
	close(fd);
	return 0;
}

Makefile

KERN_DIR = /root/tiny4412/linux-3.5
all:
	make -C $(KERN_DIR) M=`pwd` modules
	arm-linux-gcc test.c -o test
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order *o test
obj-m += linux26_class.o

Keywords: Linux Operation & Maintenance

Added by buildernaut1 on Tue, 01 Feb 2022 06:37:38 +0200