Menu Close

总线设备树驱动代码解析

此文章介绍了如何在 PetaLinux 中使用 echo 实现 gpio。

相关参考文章:SOC 教学教案

 

首先我们将添加我们将使用的组件。

/*  fii-dt-driver.c - The simplest kernel module.
* Copyright (C) 2013 - 2016 Xilinx, Inc
*
*   This program is free software; you can redistribute it and/or modify
*   it unde r the terms of the GNU General Public License as published by
*   the Free Software Foundation; either version 2 of the License, or
*   (at your option) any later version.

*   This program is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU General Public License for more details.
*
*   You should have received a copy of the GNU General Public License along
*   with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>  /* for put_user */

#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>

linux/kernel.h 表明我们在做内核工作,使我们能够使用 printk() 命令往内核 log 文件中添加信息。

linux/init.h 允许使用 module_init() 和 module_exit()。

linux/module.h 表明我们在做与内核有关的模块工作。模块是可以根据需求在内核装卸的代码段。

linux/slab.h 帮助分配矩阵。

linux/io.h 是输入输出界面的 C 包装器,并包含其他十分有用的函数。

linux/fs.h 通过内核定义设备的文件结构,比如后面的第 148 行使用 register_chrdev 命令把驱动添加到系统中。

linux/interrupt.h 允许设备打断 CPU 当前的工作并使其听从设备的指令。

linux/uaccess.h 使我们能够使用 get_user 和 put_user 命令。这包括第 86 行的 copy_to_user 命令以及第 101 行的 copy_from_user 命令。

asm/errno.h 在编译 Linux 内核时十分关键。

linux/of_address.h 包含其他处理地址的组件。

linux/of_device.h 添加 platform_device.h 以及 of_platform.h。

linux/of_platform.h 帮助处理平台驱动以及注册工作。

 

/* Standard module information, edit as appropriate */

MODULE_LICENSE("GPL");
MODULE_AUTHOR
    ("FII inc.");
MODULE_DESCRIPTION
    ("fii-dt-driver - loadable module template generated by petalinux-create -t modules");
#define DRIVER_NAME "fii-dt-driver"

MODULE_LICENSE(“GPL”) 用于压制警告说代码不是开源的。

MODULE_AUTHOR() 用于指定模块的作者。

MODULE_DESCRIPTION() 用于描述模块的用途。

#define DRIVER_NAME “fii-dt-driver” 用于指定驱动的名字。

 

static const struct of_device_id of_match_fii_dt_driver[] = {
//    {.compatible = "fii,fii-dt-driver", .data = NULL},
    {.compatible = "fii,fii-dt-driver", },
    {/*sentinel*/},
};

//platform driver

static struct class  *fii_dt_driver_class;
static unsigned int major;
static unsigned int * gpio_base;
static unsigned int * gpio_data;
static unsigned int * gpio_dir;

static int fii_dt_driver_init(void);
static int fii_dt_driver_open(struct inode* node,struct file* filp){

    gpio_data = ioremap(gpio_base, 8);

    if(gpio_data){
        printk("kernel: ioremap(0x%08x)=0x%08x \n", gpio_base, gpio_data);
    }

    else{
        return -EINVAL;
    }

    gpio_dir = gpio_data + 1;

    return 0;
}

ioremap将输入/输出设备的物理地址转换为内核中的虚拟地址。其中输入的参数为(physical_address, size),物理地址和大小。我们可以看到它的物理地址是gpio_base,并且大小为8。注意Linux下这样转换地址使其在Linux中更容易管理。其中printk帮助记载数据和运行过程。

因为gpio_data指向一个地址,所以如果它的值为0那将表示没有正确加载。因此它将返回-EINVAL,表示 “Invalid Argument”。

 

static int fii_dt_driver_release(struct platform_device * dev)
{
    iounmap(gpio_data);
    unregister_chrdev(major, "fii-dt-driver");
    device_destroy(fii_dt_driver_class,MKDEV(major,0));
    class_destroy(fii_dt_driver_class);   

    return 0;
}

这里定义了驱动出口函数,用于注销字符设备驱动。设备通过 unregister_chrdev 和 device_destroy 从系统中去除。最后 class_destroy 摧毁 struct 结构以及指针。

 

/*
    0x43c0_0000 + 4 = value
    0x43c0_0000 + 8 = direction
*/
static ssize_t fii_dt_driver_read(struct file *filp, char *buf, size_t size, loff_t *offset)
{
    unsigned char val[32];
    unsigned int temp;
    unsigned int *addr_p;
    int i,cnt;
    printk("kernel: fii-dt-driver read start.... size = %d\n", size);

    cnt = size;
    addr_p = gpio_data;
    temp = *addr_p ;

    for(i = 0; i < cnt/4; i++ )
    {
        val[i*4 + 3] = temp & 0xff;
        val[i*4 + 2] = (temp >> 8) & 0xff;
        val[i*4 + 1] = (temp >> 16) & 0xff;
        val[i*4 + 0] = (temp >> 24) & 0xff;

        addr_p ++;
        temp = *addr_p;
    }

    if(i % 4 == 1)
    {
        val[i*4 + 0] = temp & 0xff;
    }

    if(i % 4 == 2)
    {
        val[i*4 + 1] = temp & 0xff;
        val[i*4 + 0] = (temp >> 8) & 0xff;
    }

    if(i % 4 == 3)
    {
        val[i*4 + 2] = temp & 0xff;
        val[i*4 + 1] = (temp >> 8) & 0xff;
        val[i*4 + 0] = (temp >> 16) & 0xff;
    }

    copy_to_user(buf, val, cnt);

    return cnt;
}

以上代码用于从设备读取信息。把 temp 中存取的 gpio_data 考到 val 数组中。注意最下方 copy_to_user 函数的参数为 (destination, source, size);分别为目的地址,原始地址,以及大小(字节)。此函数将把原始地址大小数量的字节拷贝到目的地址。注意原始地址必须存在于内核空间,并且目的地址需要存在于用户空间。

这一段代码的主要部分为第 100 行与 126 行之间。它把数据分成 8 字节的小段,并且将其以反向顺序保存;使其在被读取的时候是正确的顺序。注意 & 0xff 用于掩盖数据,只留下最右边的 8 个字节。如果数据大小不是 4 的倍数,它将把多余的在最后附上。

 

static ssize_t fii_dt_driver_write(struct file * filp, const char __user *buf, size_t size, loff_t * offset) {
    unsigned char val[32];
    unsigned int temp = 0;
    unsigned int * addr_p;
    int i,cnt;

    memset(val,0,32);
    addr_p = gpio_data;

    printk("kernel: fii-dt-driver write start.... size = %d\n", size);

    copy_from_user(&val, buf, size);

    cnt = size - 1;

    printk("kernel: val[0] = 0x%08x \n", val[0]);

    if(val[0] == 'w')
    {
        temp = val[2];
        temp = temp << 8 | val[3];
        temp = temp << 8 | val[4];
        temp = temp << 8 | val[5];

        *addr_p = temp;

        printk("kernel: gpio_data = 0x%08x \n", temp);
    }

    else if(val[0] == 'd')
    {
        addr_p ++;
        temp = val[2];
        temp = temp << 8 | val[3];
        temp = temp << 8 | val[4];
        temp = temp << 8 | val[5];

        *addr_p = temp;

        printk("kernel: gpio_dir = 0x%08x \n", temp);
    }

    else

    {

        printk("kernel: invalid parameter \n");

    }

     

    return size;

}

以上代码用于将数据写到设备上,主要使用 copy_from_user 函数。它的参数也是 (destination, source, size)。此函数将把原始地址大小数量的字节拷贝到目的地址。注意原始地址必须存在于用户空间,并且目的地址需要存在于内核空间。

这一部分也处理应用的输入,例如用户输入的 ‘dir’ ‘wr’ 以及 ‘rd’。它将通过查看第一个字符判断用户的输入并准备相应的数据与操作,同时也往 log 文件中添加信息。

 

static struct file_operations fii_dt_driver_oprs = {
    .owner      = THIS_MODULE,
    .open       = fii_dt_driver_open,
    .write      = fii_dt_driver_write,
    .read       = fii_dt_driver_read,
};

以上部分用于处理linux/fs.h中定义的file_operations结构体。它包含指向驱动程序定义的函数的指针,以便在设备上执行操作。

 

static int fii_dt_driver_probe(struct platform_device *pdev)
{
    struct resource *res;
    printk ("kernel: enter fii_dt_driver probe ...... \n");
    printk ("kernel: enter fii_dt_driver probe ...... \n");
    printk ("kernel: enter fii_dt_driver probe ...... \n");

    res = platform_get_resource(pdev, IORESOURCE_MEM,0);

    if(res){
        gpio_base = res->start;
    }

    major=register_chrdev(0, "fii-dt-driver", &fii_dt_driver_oprs);

    if (major < 0) {
        printk ("Registering the character device failed with %d\n", major);

        return major;
    }

    fii_dt_driver_class = class_create(THIS_MODULE, "fii-dt-driver_class");
    device_create(fii_dt_driver_class,NULL,MKDEV(major,0),NULL,"fii-dt-driver");

    return 0;
}

以上部分用于处理驱动入口函数。字符设备通过设备文件访问,通常位于 /dev 目录下。

设备与内核注册后通过 linux/fs.h 下的 register_chrdev 把驱动添加到系统中。

其中 register_chrdev 的参数分别是请求的 major 数,将出现在 /proc/devices 里的设备名,以及驱动指向 file_operations 列表的指针。注意如果此函数返回一个负数,那将表示注册失败。

 

MODULE_DEVICE_TABLE(of, of_match_fii_dt_driver);

static struct platform_driver fii_dt_driver_drv = {
    .driver     = {
        .name   = "fii-dt-driver",
        .owner  = THIS_MODULE,
        .of_match_table = of_match_fii_dt_driver,
    },

    .probe      = fii_dt_driver_probe,
    .remove     = fii_dt_driver_release,
};

以上部分定义了模块设备表,用于处理platform_driver结构体。它包含指向驱动程序定义的函数的指针,以便在设备上执行操作。

 

static int fii_dt_driver_init(void){       /* register device  */
    return platform_driver_register(&fii_dt_driver_drv);
}

static void fii_dt_driver_exit(void){
    platform_driver_unregister(&fii_dt_driver_drv);
    return;
};

//module_platform_driver(fii_dt_driver_drv);

module_init(fii_dt_driver_init);
module_exit(fii_dt_driver_exit);

MODULE_ALIAS("platform:fii-dt-driver");

以上部分定义了注册以及注销设备的函数。

module_init() 定义了模块插入以及启动时运行的函数。在这里函数是 swled_init。

module_exit() 定义了模块去除时运行的函数。在返回时所有东西需要已经被清除。在这里函数是 swled_exit。

MODULE_ALIAS 用于直接为模块源提供名称,在这里是 “platform:fii-dt-driver”。

Posted in 教材与教案

发表评论

相关链接