[IMX6ULL driver development learning] 07. Platform bus device driver model and device tree of the idea of driver separation

Table of contents

1. The idea of ​​driver separation

2. Device tree

2.1 Using the device tree

3. Platform bus device driver template


1. The idea of ​​driver separation

[IMX6ULL driver development learning] 05. Character device driver development template (including read and write functions, poll mechanism, asynchronous notification, timer, interrupt, automatic creation of device nodes and ring buffer)_Aaron's blog - CSDN blog

There are many disadvantages in the code of the driver before: poor portability, when the driver is ported to other boards, the developer needs to modify the pins. , and also recompile the driver or kernel. In order to improve the efficiency of transplantation and development, the idea of ​​driver separation programming is particularly important.

First of all, we need to know: There is a structure platform_bus_type (virtual bus) in the kernel, and two linked lists are abstracted on the bus: the device linked list and the driver linked list .

When we write a driver, we can construct the platform_device structure and then add it to the kernel (platform_device_register function), which is to put it into the device list of the platform_bus_type structure.

The driver will call two functions to register the platform_device structure and platform_driver structure . The platform_device structure contains hardware resources, including register addresses, memory addresses, and interrupt numbers; the platform_driver structure contains general codes. When writing the driver in the past, I only wrote it as a .c file and registered the character device driver in the entry function; now I need to deliberately split it into two files, gpio_drv.c and gpio_dev.c.

Register the platform_driver structure in the entry function of gpio_drv.c (using the platform_driver_register function), and register the platform_device structure in the entry function of gpio_dev.c (using the platform_device_register function). This function will put the platform_device structure to be registered. The device list of the platform_bus_type structure (virtual bus) in the kernel, and the driver list of the platform_bus_type structure (virtual bus) will be traversed, and the platform_device structure will be compared with each platform_driver structure (to find a driver for the hardware device), and the match will be successful. There will be no further comparisons.

If the match is successful, the probe function in the platform_driver structure will be called. Complete in the probe function: ① Get the pin number from the platform_device structure ② Register the character device driver.

If the device (platform_device structure) was added in advance, but no matching driver (platform_driver structure) was found. When adding a driver later, the device list will be traversed, and if the match is successful, the driver's probe function will be called. And a driver may support multiple devices.

2. Device tree

The purpose of gpio_dev.c and the device tree is to construct the platform_device structure . If you use gpio_dev.c, you need to modify the pins, recompile and install it every time, resulting in many redundant gpio_dev.c files and platform_device structures in the kernel. Use the device tree: add node information in the device tree file, and according to the node information, the kernel will construct a platform_device structure .

There is a uboot when the board starts, and uboot will do two things: ① If there is an SD card on the board, and the device tree dtb file is stored in the SD card, uboot will read the device tree file into the memory; ② There is also a kernel in the SD card , uboot will read the kernel into the memory; ③ Start the kernel, uboot will pass the address of the device tree to the kernel, and the kernel will parse the device tree file into various platform_device structures at this address.

In the future, if the pin is modified in the product, we only need to modify the device tree dtb file. The kernel remains unchanged, but the device tree file is changed.

2.1 Using the device tree

  • Modify the device tree:arch/arm/boot/dts/100ask_imx6ull-14x14.dts

 Add node information in the device tree file, note that compatible matches the driver's compatible

motor {
    compatible = "100ask,gpiodemo";
    gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>, 
            <&gpio4 20 GPIO_ACTIVE_HIGH>,
            <&gpio4 21 GPIO_ACTIVE_HIGH>,
			<&gpio4 22 GPIO_ACTIVE_HIGH>;
};
  • Compile: make dtbs
  • copy to board 
PC:
cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/

开发板:
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
cp /mnt/100ask_imx6ull-14x14.dtb  /boot
reboot
  •  test
insmod gpio_drv.ko
./button_test /dev/gpio ...

3. Platform bus device driver template

Support platfrom_device from self-written .c files and modified device tree files, including interrupts, timers, reading and writing, poll mechanism, and asynchronous notification. The driver is as follows:

#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

struct gpio_desc{
	int gpio;
	int irq;
    char name[128];
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc *gpios;
static int count;

/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);

// static void key_timer_expire(struct timer_list *t)
static void key_timer_expire(unsigned long data)
{
	/* data ==> gpio */
	// struct gpio_desc *gpio_desc = from_timer(gpio_desc, t, key_timer);
	struct gpio_desc *gpio_desc = (struct gpio_desc *)data;
	int val;
	int key;

	val = gpio_get_value(gpio_desc->gpio);


	//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);
	key = (gpio_desc->key) | (val<<8);
	put_key(key);
	wake_up_interruptible(&gpio_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))
		return -EAGAIN;
	
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());
	key = get_key();
	err = copy_to_user(buf, &key, 4);
	
	return 4;
}

static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char ker_buf[2];
    int err;

    if (size != 2)
        return -EINVAL;

    err = copy_from_user(ker_buf, buf, size);
    
    if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
        return -EINVAL;

    gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);
    return 2;    
}


static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int gpio_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_drv_read,
	.write   = gpio_drv_write,
	.poll    = gpio_drv_poll,
	.fasync  = gpio_drv_fasync,
};


static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);
	mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);
	return IRQ_HANDLED;
}


/* 在入口函数 */
static int gpio_drv_probe(struct platform_device *pdev)
{
    int err = 0;
    int i;
	//平台设备里面有设备树节点信息
	//如果平台设备platform_device来自设备树的话,np就不是NULL
	struct device_node *np = pdev->dev.of_node;
	//资源指针 
	struct resource *res;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	

	/* 从platfrom_device获得引脚信息 
	 * 1. pdev来自自己写的c文件
     * 2. pdev来自设备树(在设备树文件中添加硬件的节点信息)
	 */
	
	if (np)
	{
		/* pdev来自设备树 
		设备树节点信息示例:
        reg_usb_ltemodule: regulator@1 {
            compatible = "100ask,gpiodemo";
            gpios = <&gpio5 5 GPIO_ACTIVE_HIGH>, <&gpio5 3 GPIO_ACTIVE_HIGH>;
        };
		*/
		count = of_gpio_count(np);//获得这个设备信息:多少个引脚
		if (!count)
			return -EINVAL;

		gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL);
		for (i = 0; i < count; i++)
		{
			gpios[i].gpio = of_get_gpio(np, i);//取出这个设备的第i个引脚的引脚编号
			sprintf(gpios[i].name, "%s_pin_%d", np->name, i);//给对应引脚取名字 申请gpio时需要用到名字
		}
	}
	else
	{
		/* pdev来自c文件 
		static struct resource omap16xx_gpio3_resources[] = {
			{
					.start  = 115,
					.end    = 115,
					.flags  = IORESOURCE_IRQ,
			},
			{
					.start  = 118,
					.end    = 118,
					.flags  = IORESOURCE_IRQ,
			},		};		
		*/
		count = 0;
		while (1)
		{	//获得平台设备里面的,这种IORESOURCE_IRQ类型的资源
			//@dev:platform_device  @type:resource type @num:resource index
			res = platform_get_resource(pdev, IORESOURCE_IRQ, count);
			if (res)
			{
				count++;
			}
			else
			{
				break;
			}
		}

		if (!count)
			return -EINVAL;

		gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL);
		for (i = 0; i < count; i++)
		{
			res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
			gpios[i].gpio = res->start;//取出这个设备的第i个引脚的引脚编号
			sprintf(gpios[i].name, "%s_pin_%d", pdev->name, i);//给对应引脚取名字 申请gpio时需要用到名字
		}

	}

	for (i = 0; i < count; i++)
	{		
		gpios[i].irq  = gpio_to_irq(gpios[i].gpio);

		setup_timer(&gpios[i].key_timer, key_timer_expire, (unsigned long)&gpios[i]);
	 	//timer_setup(&gpios[i].key_timer, key_timer_expire, 0);
		gpios[i].key_timer.expires = ~0;
		add_timer(&gpios[i].key_timer);
		err = request_irq(gpios[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "100ask_gpio_key", &gpios[i]);
	}

	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_gpio_key", &gpio_key_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "100ask_gpio_key_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_gpio_key");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_gpio"); /* /dev/100ask_gpio */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
//这里应该free gpio这个数组 但是没加上不知道为啥
static int gpio_drv_remove(struct platform_device *pdev)
{
    int i;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "100ask_gpio_key");

	for (i = 0; i < count; i++)
	{
		free_irq(gpios[i].irq, &gpios[i]);
		del_timer(&gpios[i].key_timer);
	}

	return 0;
}

//支持的设备
//只要设备树节点的信息它的compatible与下面的compatible相同,
//即platfrom_device和platfrom_driver匹配成功,成功后probe函数就被调用
static const struct of_device_id gpio_dt_ids[] = {
        { .compatible = "100ask,gpiodemo", },
        { /* sentinel */ }
};

static struct platform_driver gpio_platform_driver = {
	.driver		= {
		.name	= "100ask_gpio_plat_drv",
		.of_match_table = gpio_dt_ids,
	},
	.probe		= gpio_drv_probe,
	.remove		= gpio_drv_remove,
};

static int __init gpio_drv_init(void)
{
	/* 注册platform_driver */
	return platform_driver_register(&gpio_platform_driver);
}

static void __exit gpio_drv_exit(void)
{
	/* 反注册platform_driver */
	platform_driver_unregister(&gpio_platform_driver);
}

/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_drv_init);
module_exit(gpio_drv_exit);

MODULE_LICENSE("GPL");


Guess you like

Origin blog.csdn.net/qq_43460230/article/details/132352341