Embedded小散修头像
关注

linux驱动开发框架(v4.14)

1. 驱动加载以及设备节点文件的创建

驱动模块的卸载和加载

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 入口函数具体内容 */ 
    return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /* 出口函数具体内容 */
}
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init); // 注册模块加载函数
module_exit(xxx_exit); // 注册模块卸载函数
// 分配设备号中的入参name,就是/proc/devices文件下的设备名字

// 静态分配设备号
// register_chrdev()中入参major非0时,为静态分配设备号,当major为0时,动态分配设备号
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)

// 动态分配设备号
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
// dev中存放动态分配的设备号,相比静态分配,可以避免设备号冲突
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)
// 所谓的字符设备的注册,就是将设备号与struct file_operations驱动函数进行关联
// register_chrdev()在分配设备号的同时,将设备号与入参fops进行关联,完成设备注册
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)

// 使用register_chrdev_region()以及alloc_chrdev_region()分配的设备号,采用cdev_add()完成设备注册
struct cdev test_cdev;
test_cdev.owner = THIS_MODULE;
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
// 入参p中包含了要注册关联的fops,入参dev是要关联的设备号
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
void cdev_del(struct cdev *p)
// 创建设备节点后,将会在/dev文件夹下创建相应的设备节点文件,用户空间应用程序可通过操作该设备节点文件,实现对硬件的控制,创建设备节点有以下三种方法:
// 1、mknod命令手动添加设备节点
// 2、用户空间调用mknod系统调用
int mknod(const char *pathname, mode_t mode, dev_t dev);
// 3、驱动入口程序中创建类和设备,自动穿件设备节点
// 入参owner一般为THIS_MODULE,入参name为类名称,执行class_create之后,在/sys/class/目录下生成对应类名称的链接文件
struct class *class_create (struct module *owner, const char *name);
// class为class_create生成的class,入参parent一般为NULL,devt为分配的设备号,drvdata一般为NULL,最后一个参数为设备名字,执行device_create之后,会在/dev目录下创建对应设备名字的设备节点文件,应用程序就通过该文件,控制硬件设备
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
void class_destroy(struct class *cls);
void device_destroy(struct class *class, dev_t devt);
MODULE_LICENSE("GPL") //添加模块LICENSE信息,必须的,否则会编译报错
MODULE_AUTHOR("vendor") //添加模块作者信息,非必须
MODULE_DESCRIPTION("xxxx") //添加模块描述信息,非必须

2. 通过寄存器虚拟地址控制硬件

通过在驱动程序中宏定义寄存器实际的物理地址,然后获取物理地址对应的寄存器虚拟地址,通过读写寄存器虚拟地址,控制硬件的动作

// 定义在arch/arm/include/asm/io.h,可获取实际物理地址对应的虚拟地址
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
void iounmap (volatile void __iomem *addr)
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

3. 通过设备树获取设备属性

在设备树中配置compatible、status、default_state、reg等属性,通过of操作函数,获得相应的设备树节点属性,相比在驱动程序中去配置相应的设备信息(特别是寄存器、中断等信息),采用设备树配置硬件设备信息之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。使用设备树之后,许多硬件设备信息可以直接通过它传递给Linux。

linux内核在启动时,会解析设备树中各个节点设备的信息,并在根文件系统的/proc/device-tree目录(该目录实际上是一个链接文件,实际指向的地址为/sys/firmware/devicetree/base)下根据节点名字创建不同文件夹

在这里插入图片描述

可以在Linux源码目录Documentation/devicetree/bindings下查看各个硬件如何在设备树中添加节点。

OF(open firemware)函数,定义在include/linux/of.h头文件中,用于获取设备树中的节点或属性信息。

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)
inline struct device_node *of_find_node_by_path(const char *path)
struct device_node *of_get_parent(const struct device_node *node)
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
// 入参np为设备树节点,index适用于数组类型的节点属性
property *of_find_property(const struct device_node *np, const char *name, int *lenp)
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value)
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
int of_n_addr_cells(struct device_node *np)
int of_n_size_cells(struct device_node *np)
int of_device_is_compatible(const struct device_node *device, const char *compat)
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags)
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
// of_iomap()相当于of_address_to_resource() + ioremap(),相比之下代码更简洁
void __iomem *of_iomap(struct device_node *np, int index)

4. gpio子系统

4.1 gpio子系统使能及头文件包含

使用gpio子系统需要#include<linux/gpio.h>,在第46行,可知若要使用gpio子系统,需要配置CONFIG_GPIOLIB=y,当CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y时,包含的是#include <asm/gpio.h>,否则包含的是#include <asm-generic/gpio.h>

/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __LINUX_GPIO_H
#define __LINUX_GPIO_H

#include <linux/errno.h>

/* see Documentation/gpio/gpio-legacy.txt */

/* make these flag values available regardless of GPIO kconfig options */
#define GPIOF_DIR_OUT        (0 << 0)
#define GPIOF_DIR_IN        (1 << 0)

#define GPIOF_INIT_LOW        (0 << 1)
#define GPIOF_INIT_HIGH        (1 << 1)

#define GPIOF_IN                (GPIOF_DIR_IN)
#define GPIOF_OUT_INIT_LOW        (GPIOF_DIR_OUT | GPIOF_INIT_LOW)
#define GPIOF_OUT_INIT_HIGH        (GPIOF_DIR_OUT | GPIOF_INIT_HIGH)

/* Gpio pin is active-low */
#define GPIOF_ACTIVE_LOW        (1 << 2)

/* Gpio pin is open drain */
#define GPIOF_OPEN_DRAIN        (1 << 3)

/* Gpio pin is open source */
#define GPIOF_OPEN_SOURCE        (1 << 4)

#define GPIOF_EXPORT                (1 << 5)
#define GPIOF_EXPORT_CHANGEABLE        (1 << 6)
#define GPIOF_EXPORT_DIR_FIXED        (GPIOF_EXPORT)
#define GPIOF_EXPORT_DIR_CHANGEABLE (GPIOF_EXPORT | GPIOF_EXPORT_CHANGEABLE)

/**
 * struct gpio - a structure describing a GPIO with configuration
 * @gpio:        the GPIO number
 * @flags:        GPIO configuration as specified by GPIOF_*
 * @label:        a literal description string of this GPIO
 */
struct gpio {
        unsigned        gpio;
        unsigned long        flags;
        const char        *label;
};

#ifdef CONFIG_GPIOLIB

#ifdef CONFIG_ARCH_HAVE_CUSTOM_GPIO_H
#include <asm/gpio.h>
#else

#include <asm-generic/gpio.h>

static inline int gpio_get_value(unsigned int gpio)
{
        return __gpio_get_value(gpio);
}

static inline void gpio_set_value(unsigned int gpio, int value)
{
        __gpio_set_value(gpio, value);
}

static inline int gpio_cansleep(unsigned int gpio)
{
        return __gpio_cansleep(gpio);
}

static inline int gpio_to_irq(unsigned int gpio)
{
        return __gpio_to_irq(gpio);
}

static inline int irq_to_gpio(unsigned int irq)
{
        return -EINVAL;
}

#endif /* ! CONFIG_ARCH_HAVE_CUSTOM_GPIO_H */

/* CONFIG_GPIOLIB: bindings for managed devices that want to request gpios */

struct device;

int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,
                          unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);

#else /* ! CONFIG_GPIOLIB */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/bug.h>
#include <linux/pinctrl/pinctrl.h>

struct device;
struct gpio_chip;

static inline bool gpio_is_valid(int number)
{
        return false;
}

static inline int gpio_request(unsigned gpio, const char *label)
{
        return -ENOSYS;
}

static inline int gpio_request_one(unsigned gpio,
                                        unsigned long flags, const char *label)
{
        return -ENOSYS;
}

static inline int gpio_request_array(const struct gpio *array, size_t num)
{
        return -ENOSYS;
}

static inline void gpio_free(unsigned gpio)
{
        might_sleep();

        /* GPIO can never have been requested */
        WARN_ON(1);
}

static inline void gpio_free_array(const struct gpio *array, size_t num)
{
        might_sleep();

        /* GPIO can never have been requested */
        WARN_ON(1);
}

static inline int gpio_direction_input(unsigned gpio)
{
        return -ENOSYS;
}

static inline int gpio_direction_output(unsigned gpio, int value)
{
        return -ENOSYS;
}

static inline int gpio_set_debounce(unsigned gpio, unsigned debounce)
{
        return -ENOSYS;
}

static inline int gpio_get_value(unsigned gpio)
{
        /* GPIO can never have been requested or set as {in,out}put */
        WARN_ON(1);
        return 0;
}

static inline void gpio_set_value(unsigned gpio, int value)
{
        /* GPIO can never have been requested or set as output */
        WARN_ON(1);
}

static inline int gpio_cansleep(unsigned gpio)
{
        /* GPIO can never have been requested or set as {in,out}put */
        WARN_ON(1);
        return 0;
}

static inline int gpio_get_value_cansleep(unsigned gpio)
{
        /* GPIO can never have been requested or set as {in,out}put */
        WARN_ON(1);
        return 0;
}

static inline void gpio_set_value_cansleep(unsigned gpio, int value)
{
        /* GPIO can never have been requested or set as output */
        WARN_ON(1);
}

static inline int gpio_export(unsigned gpio, bool direction_may_change)
{
        /* GPIO can never have been requested or set as {in,out}put */
        WARN_ON(1);
        return -EINVAL;
}

static inline int gpio_export_link(struct device *dev, const char *name,
                                unsigned gpio)
{
        /* GPIO can never have been exported */
        WARN_ON(1);
        return -EINVAL;
}

static inline void gpio_unexport(unsigned gpio)
{
        /* GPIO can never have been exported */
        WARN_ON(1);
}

static inline int gpio_to_irq(unsigned gpio)
{
        /* GPIO can never have been requested or set as input */
        WARN_ON(1);
        return -EINVAL;
}

static inline int gpiochip_lock_as_irq(struct gpio_chip *chip,
                                       unsigned int offset)
{
        WARN_ON(1);
        return -EINVAL;
}

static inline void gpiochip_unlock_as_irq(struct gpio_chip *chip,
                                          unsigned int offset)
{
        WARN_ON(1);
}

static inline int irq_to_gpio(unsigned irq)
{
        /* irq can never have been returned from gpio_to_irq() */
        WARN_ON(1);
        return -EINVAL;
}

static inline int
gpiochip_add_pin_range(struct gpio_chip *chip, const char *pinctl_name,
                       unsigned int gpio_offset, unsigned int pin_offset,
                       unsigned int npins)
{
        WARN_ON(1);
        return -EINVAL;
}

static inline int
gpiochip_add_pingroup_range(struct gpio_chip *chip,
                        struct pinctrl_dev *pctldev,
                        unsigned int gpio_offset, const char *pin_group)
{
        WARN_ON(1);
        return -EINVAL;
}

static inline void
gpiochip_remove_pin_ranges(struct gpio_chip *chip)
{
        WARN_ON(1);
}

static inline int devm_gpio_request(struct device *dev, unsigned gpio,
                                    const char *label)
{
        WARN_ON(1);
        return -EINVAL;
}

static inline int devm_gpio_request_one(struct device *dev, unsigned gpio,
                                        unsigned long flags, const char *label)
{
        WARN_ON(1);
        return -EINVAL;
}

static inline void devm_gpio_free(struct device *dev, unsigned int gpio)
{
        WARN_ON(1);
}

#endif /* ! CONFIG_GPIOLIB */

#endif /* __LINUX_GPIO_H */

在顶层Makefile文件中

...
SRCARCH         := $(ARCH)
...
# Where to locate arch specific headers
hdr-arch  := $(SRCARCH)
...
LINUXINCLUDE    := \
                -I$(srctree)/arch/$(hdr-arch)/include \
                -I$(objtree)/arch/$(hdr-arch)/include/generated \
                $(if $(KBUILD_SRC), -I$(srctree)/include) \
                -I$(objtree)/include \
                $(USERINCLUDE)
...

可知include path中包括arch/arm/include/路径,且其位于最靠前的位置,所以对于#include <header.h>会先在此路径下进行头文件的查找,所以当CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y时,包含的是#include <asm/gpio.h>,该头文件的实际路径是arch/arm/include/asm/gpio.h

如下,从第10行可以得到,arch/arm/include/asm/gpio.h最终其实还是包含的#include <asm-generic/gpio.h>,实际最终的实现是在driver/gpio/gpiolib.c中

/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ARCH_ARM_GPIO_H
#define _ARCH_ARM_GPIO_H

#if CONFIG_ARCH_NR_GPIO > 0
#define ARCH_NR_GPIOS CONFIG_ARCH_NR_GPIO
#endif

/* Note: this may rely upon the value of ARCH_NR_GPIOS set in mach/gpio.h */
#include <asm-generic/gpio.h>

/* The trivial gpiolib dispatchers */
#define gpio_get_value  __gpio_get_value
#define gpio_set_value  __gpio_set_value
#define gpio_cansleep   __gpio_cansleep

/*
 * Provide a default gpio_to_irq() which should satisfy every case.
 * However, some platforms want to do this differently, so allow them
 * to override it.
 */
#ifndef gpio_to_irq
#define gpio_to_irq        __gpio_to_irq
#endif

#endif /* _ARCH_ARM_GPIO_H */

gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用GPIO。

gpio子系统虽然方便了驱动开发者使用gpio,但是最终还是得去操作硬件寄存器;所以在使用gpio子系统之前,我们需要向内核gpio子系统注册这一套操作硬件寄存器的“方法”。

driver/gpio/gpio-zynq.c就是xilinx官方编写的,用于向内核gpio子系统注册这一套操作硬件寄存器的“方法”。driver/gpio/Kconfig可以选择是否配置该模块。driver/gpio/gpio-zynq.c有gpio引脚的定义、各个寄存器的定义等,其通过以下层次调用,将xilinx zynq gpio controller注册到内核的gpio子系统中,subsys_initcall(zynq_gpio_init)=>platform_register(&zynq_gpio_driver)=>zynp_gpio_probe()。

在这里插入图片描述
在这里插入图片描述

4.2 gpio子系统使用

Documentation/devicetree/bindings/gpio/gpio.txt文件中描述了在设备树中gpio子系统的控制器以及节点应如何配置,ZYNQ系列的PS GPIO控制器绑定信息请查看文档Documentation/devicetree/bindings/gpio/gpio-zynq.txt。

// arch/arm/boot/dts/zynq-7000.dtsi中定义的gpio controller
gpio0: gpio@e000a000 {
    compatible = "xlnx,zynq-gpio-1.0";
    #gpio-cells = <2>; // 用于指定使用该GPIO控制器时需要多少个单元格(cell)来描述一个GPIO值,<2>表示需要2个32位数值来完整描述一个GPIO
    clocks = <&clkc 42>;
    gpio-controller;
    interrupt-controller;
    #interrupt-cells = <2>;
    interrupt-parent = <&intc>;
    interrupts = <0 20 4>;
    reg = <0xe000a000 0x1000>;
};

// 自定义适配的dts中定义的gpio node
key {
    compatible = "vendor,key";
    status = "okay";
    key-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; // 表明该节点隶属于gpio0控制器,GPIO pin number, GPIO flags
    interrupt-parent = <&gpio0>;
    interrupts = <12 IRQ_TYPE_EDGE_BOTH>;
};

beeper {
    compatible = "vendor,beeper";
    status = "okay";
    default-state = "off";
    beeper-gpios = <&gpio0 60 GPIO_ACTIVE_HIGH>;
};
// 定义在include/linux/asm-generic/gpio.h,最终调用driver/gpio/gpiolib.c中的实现
int gpio_request(unsigned gpio, const char *label)
void gpio_free(unsigned gpio)
int gpio_direction_input(unsigned gpio)
int gpio_direction_output(unsigned gpio, int value)
#define gpio_get_value __gpio_get_value 
    int __gpio_get_value(unsigned gpio)
#define gpio_set_value __gpio_set_value
    void __gpio_set_value(unsigned gpio, int value)
// 定义在[include/linux/](https://elixir.bootlin.com/linux/v4.14/source/include/linux/of_gpio.h)[of_gpio.h](https://elixir.bootlin.com/linux/v4.14/source/include/linux/of_gpio.h)
int of_gpio_named_count(struct device_node *np, const char *propname)
int of_gpio_count(struct device_node *np)
int of_get_named_gpio(struct device_node *np, const char *propname, int index)

通过gpio子系统API函数以及与gpio相关的OF函数,可以简化驱动开发者的开发难度,对驱动开发者而言,gpio子系统屏蔽掉了寄存器等等信息(当然,这部分工作实际上是转化为由相应的设备厂商去完成)。相比之下,软件驱动与硬件设备之间更加解耦了。

5. platform设备驱动

设备和驱动分层,设备负责描述硬件,包括硬件的寄存器信息、硬件寄存器的配置等,驱动负责读取设备树,注册设备(即分配设备号、绑定设备号与file_operations、编写file_operations用以建立驱动与应用的关系等)。

总线则用来作为统一的接口,建立设备与驱动之间的联系,主要是通过struct bus_tpye中的int (*match)(struct device *dev, struct device_driver *drv)函数完成设备与驱动的匹配,其入参device和device_driver类型,也就是设备和驱动。

具体参见文档:Linux platform平台驱动

如果对你有帮助,欢迎点赞收藏,有相关问题也可以评论讨论,后续将继续更新该系列。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/zhy132533/article/details/166600048

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--