T7 平台总线

1.设备驱动模型

1.1由来

  • 在之前的字符设备驱动编程模型里面主要有以下几步

1.首先要实现入口函数xxx_init()和卸载函数xxx_exit()

2.申请设备号register_chrdev

3.创建设备节点,如class_create,device_create

4.硬件部分初始化,如io资源映射ioremap,中断注册等

5.构建file_operation结构

6.实现xxx_open,xxx_read,xxx_write等函数

  • 这样写看似没毛病,但是可移植性较差.如果在写完一个驱动的情况下又要写另外一个驱动,明显按照这种写法又得推倒重来
  • 举个例子,假如要写手机摄像头驱动,使用上述方法可写一套前摄驱动和一套后摄驱动,但实际上我们发现除了步骤4不一样,其他的步骤前后摄都差不多,因此上述方法在代码复用性上有待提升,换种说法,就是为了省事,少加班
  • 因此隆重推出linux设备驱动模型

1.2概念

  • 该模型将驱动划分为3个部分:Device(设备对象),Driver(设备驱动对象),Bus(总线对象)
  • 其中可以把Device想象为车,Driver想象为老司机,每辆车的具体参数不一样,如功耗,百公里加速,油耗等等.但是对于老司机而言其驾驶方式都是一样的.其驾驶步骤都差不多
  • 在驱动里面,Device也就是针对不同硬件,其对于的操作地址或者中断不一样,但是对于Driver部分而言都是差不多的,都可以使用ioremap函数映射地址,之后通过readl或者writel函数操作地址
  • 因此在写驱动代码的时候就需要将Device与Driver相分类,前者的核心是存异,后者的核心是求同.因此存异的代价是数量繁多,针对不同设备就需要不同的代码,但是其代码量是比较少的.而求同的代价是代码量多,但是可做到一劳永逸
  • 既然在写代码的时候将Device与Driver相分离了,但是到了最后还是会将二者组合起来.怎么组合呢?故引入Bus(总线对象)来将Device与Driver进行组合
  • 我们将设备信息用Device对象描述好(封装为一个节点),再将设备操作方法用Driver对象描述好(封装为一个节点),之后将二者交给Bus总线(设备注册),Bus总线则根据名称将Device与Driver相匹配,进而完成老司机开车的壮举,在内核中通过链表管理
  • 如果Device与Driver匹配成功,Bus将调用proke方法

1.3结合实际分析

  • 先看一张图

/sys下面有总线文件夹,设备文件夹和类文件夹.

总线文件夹保存总线信息,如i2c总线,usb总线;

设备文件夹下面保存设备信息,如usb设备

在类文件夹下保存事件信息

在某一总线下面又包含设备和驱动,驱动即driver,设备即device,其中的设备通过软链接的方式指向/sys/devices中的设备

  • 我们进入/sys目录查看其中的文件,可以看到其中有bus总线目录,devices设备目录(记录设备信息)
topeet@ubuntu:~$ cd /sys
topeet@ubuntu:/sys$ ls
block  bus  class  dev  devices  firmware  fs  hypervisor  kernel  module  power
topeet@ubuntu:/sys/devices$ cd /sys/devices/
topeet@ubuntu:/sys/devices$ ls
breakpoint  cpu  LNXSYSTM:00  pci0000:00  platform  pnp0  rapidio  software  system  tracepoint  virtual
topeet@ubuntu:/sys/devices$ 
  • 之后进入/sys/bus目录,可看到其中有很多总线,以usb总线为例子,在其内部就有devices设备文件和drivers驱动文件
topeet@ubuntu:/sys/bus$ ls
ac97         cpu           hid           mdio_bus  node         platform  scsi   spi     xen
acpi         event_source  i2c           memory    pci          pnp       sdio   usb     xen-backend
clocksource  gameport      machinecheck  mmc       pci_express  rapidio   serio  virtio
topeet@ubuntu:/sys/bus$ cd usb/
topeet@ubuntu:/sys/bus/usb$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent
  • 之前写驱动会在/dev目录下生成相应节点,但是却不能查看其详细信息,如输入设备事件,我们直接使用ls /dev/input/event*指令查看输入设备事件节点,但是却看不到其对于驱动信息,其具体信息保存在/sys/class/input下面
# 查看输入设备节点
topeet@ubuntu:~$ ls /dev/input/event*
/dev/input/event0  /dev/input/event1  /dev/input/event2  /dev/input/event3  /dev/input/event4
# 查看输入设备节点信息
topeet@ubuntu:~$ cd /sys/class/input/
topeet@ubuntu:/sys/class/input$ ls
event0  event2  event4  input1  input3  js0   mouse0  mouse2
event1  event3  input0  input2  input4  mice  mouse1
# 查看event0节点设备号
topeet@ubuntu:/sys/class/input$ cd event0
topeet@ubuntu:/sys/class/input/event0$ ls
dev  device  power  subsystem  uevent
topeet@ubuntu:/sys/class/input/event0$ cat uevent 
MAJOR=13
MINOR=64
DEVNAME=input/event0
# 查看event0节点设备号
topeet@ubuntu:/sys/class/input/event0$ ls -l /dev/input/event0
crw-r----- 1 root root 13, 64 Nov 12 02:43 /dev/input/event0
# 查看event0对于驱动名称
topeet@ubuntu:/sys/class/input/event0$ cd device
topeet@ubuntu:/sys/class/input/event0/device$ ls
capabilities  device  event0  id  modalias  name  phys  power  properties  subsystem  uevent  uniq
topeet@ubuntu:/sys/class/input/event0/device$ cat name 
Power Button
topeet@ubuntu:/sys/class/input/event0/device$ cat uevent 
PRODUCT=19/0/1/0
NAME="Power Button"
PHYS="LNXPWRBN/button/input0"
PROP=0
EV=3
KEY=10000000000000 0
MODALIAS=input:b0019v0000p0001e0000-e0,1,k74,ramlsfw

1.4构建自己的总线

  • 系统默认创建的总线在/sys/bus/文件夹下
  • 我们也可以属于创建自己的总线,我们需要在/sys/bus/下面创建我们自己的总线mybus,之后在mybus内部实现driver驱动和device设备软链接,其指向/sys/devices/目录下的mydevice设备,如图
  • 首先有一个总线对象struct bus_type,用于描述一个总线,管理device和driver,完成二者匹配
struct bus_type {
    const char      *name;
    const char      *dev_name;
    struct device       *dev_root;
    const struct attribute_group **bus_groups;
    const struct attribute_group **dev_groups;
    const struct attribute_group **drv_groups;

    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);

    int (*online)(struct device *dev);
    int (*offline)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    int (*num_vf)(struct device *dev);

    const struct dev_pm_ops *pm;

    const struct iommu_ops *iommu_ops;

    struct subsys_private *p;
    struct lock_class_key lock_key;
};
  • 可以看到,其中有很多接口,我们重点关注以下几个即可
struct bus_type {
    const char      *name;
    int (*match)(struct device *dev, struct device_driver *drv);
};
  • 之后注册与注销总线
int bus_register(struct bus_type * bus);    //非0代表失败
void bus_unregister(struct bus_type * bus);
  • 完整代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


struct bus_type mybus = {
    .name = "mybus",
};


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //注册总线
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸载总线
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
  • 现象,当安装完驱动后我们在/sys/bus目录下可看到自己的总线mybus成功被创建,进入其目录下我们会发现系统已经自动创建了系列文件,但是drivers和devices文件夹为空,这部分需要我们自己去实现
[root@iTOP-4412]# insmod my_bus.ko 
[root@iTOP-4412]# cd /sys/bus/
[root@iTOP-4412]# ls
amba         cpu          iio          mybus        sdio         workqueue
clockevents  gpio         mdio_bus     nvmem        serio
clocksource  hid          mipi-dsi     platform     spi
container    i2c          mmc          scsi         usb
[root@iTOP-4412]# cd mybus/
[root@iTOP-4412]# ls
devices            drivers_autoprobe  uevent
drivers            drivers_probe
[root@iTOP-4412]# cd devices/
[root@iTOP-4412]# cd ..
[root@iTOP-4412]# cd drivers/
[root@iTOP-4412]# 

1.5device与driver注册

1.5.1概述

  • 上述过程实现了总线的创建,但是其内部device与driver为空,需要我们自己去创建和注册
  • 由于在内核内部会自动帮我们完成device与driver的匹配,所以二者的注册顺序不论先后

1.5.2device注册与卸载

  • 内核里面有一个device设备对象,用于描述设备信息,包括地址,中断号,自定义数据等
  • device对象属性如下
struct device {
    struct device       *parent;

    struct device_private   *p;

    struct kobject kobj;        //所有对象的父类
    const char      *init_name; /* initial name of the device */
    const struct device_type *type;

    struct mutex        mutex;  /* mutex to synchronize calls to
                     * its driver.
                     */

    struct bus_type *bus;       /* type of bus device is on */
    struct device_driver *driver;   /* which driver has allocated this
                       device */
    void        *platform_data; /* Platform specific data, device
                       core doesn't touch it */
    void        *driver_data;   /* Driver data, set and get with
                       dev_set/get_drvdata */
    struct dev_links_info   links;
    struct dev_pm_info  power;
    struct dev_pm_domain    *pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
    struct irq_domain   *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
    struct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
    struct list_head    msi_list;
#endif

#ifdef CONFIG_NUMA
    int     numa_node;  /* NUMA node this device is close to */
#endif
    const struct dma_map_ops *dma_ops;
    u64     *dma_mask;  /* dma mask (if dma'able device) */
    u64     coherent_dma_mask;/* Like dma_mask, but for
                         alloc_coherent mappings as
                         not all hardware supports
                         64 bit addresses for consistent
                         allocations such descriptors. */
    unsigned long   dma_pfn_offset;

    struct device_dma_parameters *dma_parms;

    struct list_head    dma_pools;  /* dma pools (if dma'ble) */

    struct dma_coherent_mem *dma_mem; /* internal for coherent mem
                         override */
#ifdef CONFIG_DMA_CMA
    struct cma *cma_area;       /* contiguous memory area for dma
                       allocations */
#endif
    /* arch specific additions */
    struct dev_archdata archdata;

    struct device_node  *of_node; /* associated device tree node */
    struct fwnode_handle    *fwnode; /* firmware device node */

    dev_t           devt;   /* dev_t, creates the sysfs "dev" */
    u32         id; /* device instance */

    spinlock_t      devres_lock;
    struct list_head    devres_head;

    struct klist_node   knode_class;
    struct class        *class;
    const struct attribute_group **groups;  /* optional groups */

    void    (*release)(struct device *dev);     /*使用platform_device_register函数时候需要实现*/
    struct iommu_group  *iommu_group;
    struct iommu_fwspec *iommu_fwspec;

    bool            offline_disabled:1;
    bool            offline:1;
    bool            of_node_reused:1;
};
  • 我们需要注意以下几个成员
struct device {
    struct kobject kobj;            //所有对象的父类,类似于继承父类
    const char      *init_name;     //在总线中/sys/bus/devices中会创建一个该名字命名的文件,用于匹配
    struct bus_type *bus;           //指向该device对象依附的总线对象
    struct device_driver *driver;    //描述该device被哪个driver驱动
    void        *platform_data;     //自定义数据,可指向任何数据类型的数据
};
  • device注册和注销的方法
int device_register(struct device * dev);   //小于0代表设备注册失败
int device_unregister(struct device * dev);
  • device驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


/*导入外部结构体变量,使得编译通过*/
extern struct bus_type mybus;
struct device mydev = {
    .init_name = "mydev",
    .bus = &mybus,
};

static int __init mydev_init(void)
{
    int dr_ret;
    /*注册device到总线中去*/
    dr_ret = device_register(&mydev);
    if(dr_ret < 0)
    {
        printk("device_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydev_exit(void)
{
    device_unregister(&mydev);
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
  • 总线驱动代码,将自定义总线导出,便于驱动之间的相互调用
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


struct bus_type mybus = {
    .name = "mybus",
};

/*导出结构体,方便内核之间交互*/
EXPORT_SYMBOL(mybus);


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //注册总线
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸载总线
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");

  • 现象,安装my_bus.ko驱动后在/sys/bus/下出现自己的总线.安装my_dev.ko后在/sys/bus/mybus/devices/出现自己的设备
[root@iTOP-4412]# ls
key_drv.ko  my_bus.ko   my_dev.ko
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          nvmem        serio
clockevents  gpio         mdio_bus     platform     spi
clocksource  hid          mipi-dsi     scsi         usb
container    i2c          mmc          sdio         workqueue
[root@iTOP-4412]# insmod my_bus.ko 
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          mybus        sdio         workqueue
clockevents  gpio         mdio_bus     nvmem        serio
clocksource  hid          mipi-dsi     platform     spi
container    i2c          mmc          scsi         usb
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
mydev
[root@iTOP-4412]# 

1.5.3driver注册与卸载

  • 同device一样,driver也有一个设备驱动对象,他主要用于描述设备驱动的方法(代码逻辑,即如何操作地址中断等)
  • driver设备驱动对象属性如下
struct device_driver {
    const char      *name;          //在总线中/sys/bus/drivers中会创建一个该名字命名的文件,用于匹配
    struct bus_type     *bus;       //指向该driver对象依附的总线对象

    struct module       *owner;
    const char      *mod_name;  /* used for built-in modules */

    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */
    enum probe_type probe_type;

    const struct of_device_id   *of_match_table;
    const struct acpi_device_id *acpi_match_table;

    int (*probe) (struct device *dev);      //device与driver匹配之后,driver要干的事情
    int (*remove) (struct device *dev);     //device与driver从总线移除后,driver要干的事情
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;

    const struct dev_pm_ops *pm;

    struct driver_private *p;
};
  • driver注册和注销的方法
int driver_register(struct device_driver * drv);    //小于0代表驱动注册失败
void driver_unregister(struct device_driver * drv);
  • driver驱动代码如下
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


int mydrv_probe (struct device *dev)
{
    return 0;
}
int mydrv_remove (struct device *dev)
{
    return 0;
}


/*导入外部结构体变量,使得编译通过*/
extern struct bus_type mybus;
struct device_driver mydrv = {
    .name = "mydrv",
    .bus = &mybus,
    .probe = mydrv_probe,
    .remove = mydrv_remove,
};

static int __init mydrv_init(void)
{
    int dr_ret;
    /*注册driver到总线中去*/
    dr_ret = driver_register(&mydrv);
    if(dr_ret < 0)
    {
        printk("driver_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydrv_exit(void)
{
    driver_unregister(&mydrv);
}

module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
  • 同时要修改Makefile,让其能够同时编译my_bus.c,my_dev.c,my_drv.c
ROOTFS_DIR = /mnt/hgfs/share_drv/driver_ko

# 应用程序代码名称
#APP_NAME = key_test
# 驱动代码名称
MODULE_NAME = my_bus
MODULE_NAME1 = my_dev
MODULE_NAME2 = my_drv

CROSS_COMPILE = /usr/local/arm/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-linux-
CC = $(CROSS_COMPILE)gcc
ifeq ($(KERNELRELEASE), )
KERNEL_DIR = /home/topeet/kernel/itop4412_kernel_4_14_2_bsp/linux-4.14.2_iTop-4412_scp
CUR_DIR = $(shell pwd)

all:
        make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
#       $(CC) $(APP_NAME).c -o $(APP_NAME)
clean:
        make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
        rm $(APP_NAME)
install:
        cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)
else
        obj-m += $(MODULE_NAME).o
        obj-m += $(MODULE_NAME1).o
        obj-m += $(MODULE_NAME2).o
endif
  • 执行结果,可见在安装完dev与drv驱动后在/sys/bus/mybus/devices//sys/bus/mybus/drivers目录下有对应驱动文件出现
[root@iTOP-4412]# ls
key_drv.ko  my_bus.ko   my_dev.ko   my_drv.ko
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          nvmem        serio
clockevents  gpio         mdio_bus     platform     spi
clocksource  hid          mipi-dsi     scsi         usb
container    i2c          mmc          sdio         workqueue
[root@iTOP-4412]# insmod my_bus.ko 
[  525.038385] my_bus: loading out-of-tree module taints kernel.
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          mybus        sdio         workqueue
clockevents  gpio         mdio_bus     nvmem        serio
clocksource  hid          mipi-dsi     platform     spi
container    i2c          mmc          scsi         usb
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
mydev
[root@iTOP-4412]# ls /sys/bus/mybus/drivers
[root@iTOP-4412]# insmod my_drv.ko 
[root@iTOP-4412]# ls /sys/bus/mybus/drivers
mydrv
[root@iTOP-4412]# 

1.5.4手动匹配

  • 1.5.3中说到,device与driver匹配之后,会调用driver中的probe方法,但是在上述代码中driver的名字为mydrv(.name = "mydrv",),device的名字为mydev(.init_name = "mydev",).可见二者名字不同,那么自然无法匹配
  • 如何完成匹配,并且调用probe方法呢?此时需要我们自己在bus中实现匹配的逻辑过程,并且将device与driver中的名字改为一样,即总线作用之一就是匹配device和driver
  • 我们可以通过实现bus对象中的match方法完成匹配
//注意:如果匹配成功,match方法一定要返回1;匹配失败会返回0
int (*match)(struct device *dev, struct device_driver *drv);

/*demo*/
int mybus_match(struct device *dev, struct device_driver *drv)
{
    /*注意参数2为kobj对象中的name,而不是dev->init_name*/
    /*匹配成功返回0,取反后为true*/
    if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name)))
    {
        printk("match ok\n");
        return 1;
    }
    else
    {
        printk("match failed\n");
        return 0;
    }
}

特别注意:在执行strncmp函数的时候,参数1为device_driver对象中的name属性,参数2不能是device对象中的init_name属性,因为在内核中会将device对象中的init_name值赋给kobject对象中的name,然后将自己置为NULL,如果使用dev->init_name会出现段错误,其中kobject对象如下,其中init_name的值就给了kobject中的name

  • kobject对象
struct kobject {
    const char      *name;
    struct list_head    entry;
    struct kobject      *parent;
    struct kset     *kset;
    struct kobj_type    *ktype;
    struct kernfs_node  *sd; /* sysfs directory entry */
    struct kref     kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
    struct delayed_work release;
#endif
    unsigned int state_initialized:1;
    unsigned int state_in_sysfs:1;
    unsigned int state_add_uevent_sent:1;
    unsigned int state_remove_uevent_sent:1;
    unsigned int uevent_suppress:1;
};

1.5.5完整总线框架

  • bus总线
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

int mybus_match(struct device *dev, struct device_driver *drv)
{
    if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name)))
    {
        printk("match ok\n");
        return 1;
    }
    else
    {
        printk("match failed\n");
        return 0;
    }
}


struct bus_type mybus = {
    .name = "mybus",
    .match = mybus_match,
};

/*导出结构体,方便内核之间交互*/
EXPORT_SYMBOL(mybus);


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //注册总线
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸载总线
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
  • device设备
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


void mydev_release (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
}


/*导入外部结构体变量,使得编译通过*/
extern struct bus_type mybus;
struct device mydev = {
    .init_name = "myderv",
    .bus = &mybus,
    .release = mydev_release,
};

static int __init mydev_init(void)
{
    int dr_ret;
    /*注册device到总线中去*/
    dr_ret = device_register(&mydev);
    if(dr_ret < 0)
    {
        printk("device_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydev_exit(void)
{
    device_unregister(&mydev);
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
  • driver驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

int mydrv_probe (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
int mydrv_remove (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}


/*导入外部结构体变量,使得编译通过*/
extern struct bus_type mybus;
struct device_driver mydrv = {
    .name = "myderv",
    .bus = &mybus,
    .probe = mydrv_probe,
    .remove = mydrv_remove,
};

static int __init mydrv_init(void)
{
    int dr_ret;
    /*注册driver到总线中去*/
    dr_ret = driver_register(&mydrv);
    if(dr_ret < 0)
    {
        printk("driver_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydrv_exit(void)
{
    driver_unregister(&mydrv);
}

module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
  • 结果如下,可以看到,当注册完device与driver后会调用bus中的match方法匹配,匹配成功后,系统会自动调用driver对象中的probe方法
[root@iTOP-4412]# insmod my_bus.ko 
[   29.385382] my_bus: loading out-of-tree module taints kernel.
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# insmod my_drv.ko 
[   45.094375] match ok
[   45.095171] ------mydrv_probe------
[root@iTOP-4412]# 

1.6driver驱动device

1.6.1概述

  • 当driver与device匹配成功后,会调用driver中的probe方法,其中probe方法如下,其参数为一个device对象.换句话而言,我们在创建总线后会注册driver和device,当二者匹配成功后系统会将device作为参数告诉driver,以便让driver进行操作.再进一步而言,我们在device里面写好设备信息然后传给driver去使用
int (*probe) (struct device *dev);

1.6.2自定义device设备属性

  • 在1.5.2里面说到,在device对象中有一个属性为自定义数据类型,如下,因为该参数为一个void类型指针,故可以接收任何数据类型,包括结构体(对象)
void        *platform_data;     //自定义数据,可指向任何数据类型的数据
  • 为了方便device创建自定义对象,driver接收自定义对象,在此我创建一个头文件dev_info.h来声明一个类,用于保存device设备属性
#ifndef __DEV_INFO_H__
#define __DEV_INFO_H__

/*自定义一个数据类,描述设备特性*/
struct mydev_desc {
    char *name;
    int irqno;
    unsigned long addr;
};

#endif

1.6.3device设备添加自定义属性

  • 我们可创建自定义对象,在对象中描述设备属性,之后将对象作为platform_data参数传进device结构体中,如下
struct mydev_desc mydev_info = {
    .name = "test_dev",
    .irqno = 999,
    .addr = 0x30006000,
};
struct device mydev = {
    .init_name = "myderv",
    .bus = &mybus,
    .release = mydev_release,
    .platform_data = &mydev_info,
};

1.6.4driver接收设备属性

  • 首先创建一个mydev_desc类型的对象,用于接收probe方法中device参数中的设备属性,如下
struct mydev_desc *pdesc;
int mydrv_probe (struct device *dev)
{
    unsigned long *paddr;
    printk("------%s------\n", __FUNCTION__);
    /*注意强制类型转换*/
    pdesc = (struct mydev_desc *)dev->platform_data;
    printk("name = %s\n", pdesc->name);
    printk("irqno = %d\n", pdesc->irqno);
    paddr = ioremap(pdesc->addr, 8);
    return 0;
}

1.6.5完整代码

  • 描述设备属性的头文件
#ifndef __DEV_INFO_H__
#define __DEV_INFO_H__

/*自定义一个数据类,描述设备特性*/
struct mydev_desc {
    char *name;
    int irqno;
    unsigned long addr;
};
#endif
  • 总线代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

int mybus_match(struct device *dev, struct device_driver *drv)
{
    if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name)))
    {
        printk("match ok\n");
        return 1;
    }
    else
    {
        printk("match failed\n");
        return 0;
    }
}


struct bus_type mybus = {
    .name = "mybus",
    .match = mybus_match,
};

/*导出结构体,方便内核之间交互*/
EXPORT_SYMBOL(mybus);


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //注册总线
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸载总线
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
  • device设备部分代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include "dev_info.h"


struct mydev_desc mydev_info = {
    .name = "test_dev",
    .irqno = 999,
    .addr = 0x30006000,
};
void mydev_release (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
}


/*导入外部结构体变量,使得编译通过*/
/*此处填写设备专有的信息*/
extern struct bus_type mybus;
struct device mydev = {
    .init_name = "myderv",
    .bus = &mybus,
    .release = mydev_release,
    .platform_data = &mydev_info,
};

static int __init mydev_init(void)
{
    int dr_ret;
    /*注册device到总线中去*/
    dr_ret = device_register(&mydev);
    if(dr_ret < 0)
    {
        printk("device_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydev_exit(void)
{
    device_unregister(&mydev);
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
  • driver驱动部分代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/io.h>
#include "dev_info.h"


struct mydev_desc *pdesc;

/*设备与驱动匹配成功就提取设备信息*/
int mydrv_probe (struct device *dev)
{
    unsigned long *paddr;
    printk("------%s------\n", __FUNCTION__);
    /*此处注意强转*/
    pdesc = (struct mydev_desc *)dev->platform_data;
    printk("name = %s\n", pdesc->name);
    printk("irqno = %d\n", pdesc->irqno);
    paddr = ioremap(pdesc->addr, 8);
    return 0;
}
int mydrv_remove (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}


/*导入外部结构体变量,使得编译通过*/
extern struct bus_type mybus;
struct device_driver mydrv = {
    .name = "myderv",
    .bus = &mybus,
    .probe = mydrv_probe,
    .remove = mydrv_remove,
};

static int __init mydrv_init(void)
{
    int dr_ret;
    /*注册driver到总线中去*/
    dr_ret = driver_register(&mydrv);
    if(dr_ret < 0)
    {
        printk("driver_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydrv_exit(void)
{
    driver_unregister(&mydrv);
}

module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
  • 最终实现效果,当匹配成功后,driver驱动成功拿到了device设备信息
[root@iTOP-4412]# insmod my_bus.ko 
[  207.194598] my_bus: loading out-of-tree module taints kernel.
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# insmod my_drv.ko 
[  220.166648] match ok
[  220.167439] ------mydrv_probe------
[  220.176020] name = test_dev
[  220.177356] irqno = 999
[root@iTOP-4412]# 

2.平台总线模型

2.1由来

  • 平台总线模型用于平台升级
  • 假设某个硬件平台升级了,例如三星,由2440升级到4412,很明显后者处理能力要比前者强.但是后者的诞生并不是完全颠覆前者.而是继承了前者的一些特性,例如串口,IIC,GPIO等外设的控制方式等.在控制层面二者基本相同.但是后者由于内存容量的提升,那么其寻址范围要比前者更大,即寄存器地址不一样
  • 如果不用平台总线的话,对于soc升级的时候,对于相似的设备驱动,需要编写很多重复代码.因此引入平台总线模型,将设备与驱动分离,故在升级的时候只需要更改设备信息即可
  • 最终实现一个驱动驱动多个平台的设备

2.2概念

  • 平台总线(platform bus)三元素:bus(总线),driver(驱动),device(设备)

2.2.1bus

  • 在平台总线里面,bus不需要自己创建,在开机的时候由系统创建.其会在/sys/bus/下面生成一个platform总线
topeet@ubuntu:~$ cd /sys/bus/
topeet@ubuntu:/sys/bus$ ls
ac97  clocksource  event_source  hid  machinecheck  memory  node  pci_express  pnp      scsi  serio  usb     xen
acpi  cpu          gameport      i2c  mdio_bus      mmc     pci   platform     rapidio  sdio  spi    virtio  xen-backend
# 系统生成的platform总线
topeet@ubuntu:/sys/bus$ cd platform/
topeet@ubuntu:/sys/bus/platform$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent
topeet@ubuntu:/sys/bus/platform$
struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_groups = platform_dev_groups,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};
  • bus匹配规则

1.优先匹配driver中的id_table,在其中包含了支持不同的平台的名字

2.直接匹配driver中的名字和device中的名字,与1.5类似

2.2.2device

  • 平台设备对象描述如下,直接继承了device类,但是又增加了一些私有属性,总体偏向于记录设备属性
struct platform_device {
    const char  *name;          //与driver做匹配使用
    int     id;                //一半直接为-1
    bool        id_auto;
    struct device   dev;        //继承了1.5中的device父类
    u32     num_resources;      //资源的个数
    struct resource *resource;  //资源,包括了某个设备的地址或者中断

    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

2.2.3driver

  • 平台驱动对象描述如下,继承了driver父类并增加了私有方法,总体偏向于实现某种方法.
struct platform_driver {
    int (*probe)(struct platform_device *);         //platform_device与platform_driver匹配之后,platform_driver要干的事情
    int (*remove)(struct platform_device *);        //platform_device与platform_driver从总线移除后,platform_driver要干的事情
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;                   //继承driver父类
    const struct platform_device_id *id_table;      //记录该platform_driver所支持的平台
    bool prevent_deferred_probe;
};

2.3平台总线匹配原理

  • 我们可以研究以下平台设备与平台驱动之间是如何匹配的
  • 由2.2部分可看出,platform_device与platform_driver类均继承了自己的父类,分别为device与driver.并且实际注册到平台总线中的是其父类(device与driver)
  • 那么在注册的时候是注册的父类,但是在比较的时候却是子类,此处就需要通过父类来推测子类
  • 在linux内核中可通过container_of宏来获取父类指针,即如果一个结构体内部(子类)包含一个结构体(父类),我们就可以根据被包含的结构体地址去获取包含的结构体的地址,即由父类地址去获取子类地址
#define to_platform_device(x) container_of((x), struct platform_device, dev)


static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}



static int platform_match(struct device *dev, struct device_driver *drv)
{
    /*此处由父类地址去获取子类地址*/
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* When driver_override is set, only bind to the matching driver */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* Attempt an OF style match first */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;
    
     /*如果platform_driver中有id_table,则优先匹配pdrv中的设备,否则匹配drvice中的名字*/
    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}

2.4编写platform_device

  • 实操:此处使用平台总线完成点灯操作
  • 我们需要注册一个platform_device对象并定义好相关的资源:地址,中断...
  • 注册API为platform_device_register或者platform_device_add
/*注册设备,需要实现platform_device->dev->release方法*/
int platform_device_register(struct platform_device *pdev);

/*注册设备*/
int platform_device_add(struct platform_device * pdev);

/*卸载设备*/
void platform_device_unregister(struct platform_device *pdev);
  • 在platform_device对象中我们需要填写硬件资源信息,其保存在resource结构体内,注意:一般某个设备可能包含多个资源,因此可以通过使用结构体数组来描述多个资源信息
struct resource {
    resource_size_t start;          //起始地址
    resource_size_t end;            //结束地址
    const char      *name;          //自定义名字
    unsigned long   flags;          //标志,表示当前资源是描述内存(IORESOURCE_MEM)还是中断(IORESOURCE_IRQ)
    struct resource *parent, *sibling, *child;
};
  • 知道大概原理后我们查看开发板原理图,其中有2个LED灯可控,如图.其中我们可以发现其使用了2个引脚控制,一个为KP_COL0,一个为VDD50_EN,此可我们需要跳转到核心板原理图去寻找两个引脚
  • LED2与LED3分别对应芯片的GPL2_0GPK1_1引脚,之后再去查找芯片数据手册
  • 首先查找GPL2组寄存器,发现其对应配置寄存器GPL2CON描述如下,可以看出其基地址为0x1100 0000(#define GPL_BASE 0x11000000),偏移地址为0x0100,所以描述资源的起始地址可设置为0x1100 0100(#define GPL2_CON GPL_BASE + 0x0100),而资源的终止地址则为起始地址加上GPL2系列地址的总长度(24Byte)
  • GPL2系列寄存器如下,可以看到在GPL2系列中一共有6个寄存器,其中每个寄存器大小为4字节,因此GPL2系列大小为24字节(#define GPL2_SIZE 24),因此结束地址为GPL2_CON + GPL2_SIZE - 1.特别注意:最后要减去1,假设GPL2CON编号为1,那么GPL2PUDPDN编号为6,即1 + 6 - 1 = 6,所以要减去1.
  • 对于LED3的GPK1系列就不必多说了,最终填写的资源描述如下
#define GPL_BASE 0x11000000
#define GPL2_CON GPL_BASE + 0x0100
#define GPL2_SIZE 24

#define GPK_BASE 0x11000000
#define GPK1_CON GPK_BASE + 0x0060
#define GPK1_SIZE 24

/*定义为一个数组,因为一个设备中可能有多个资源*/
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意减去1个偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
};
  • 再说明一下对中断的描述,中断资源不同于内存资源,中断的描述是通过中断号来体现,因此其开始与结束都是同一个地址,例如
#define IRQ_EINT(x)    (((x) >= 4) ? (IRQ_EINT4 + (x) - 4) : (IRQ_EINT0 + (x)))

struct resource led_res[] = {
    /*中断资源,此处描述4号中断*/
    [0] = {
        .start = IRQ_EINT(4),
        .end = IRQ_EINT(4),
        .flags = IORESOURCE_IRQ,
    },
};
  • 最后在platform_device结构体中指定资源地址和大小即可
struct platform_device led_pdev = {
    .name = "exynos4412_led",               //用作匹配
    .id = -1,                               //一般取-1,之后会详解
    .num_resources = ARRAY_SIZE(led_res),   //资源个数,此处为2
    .resource = led_res,                    //资源数组
};
  • 完整platform_device代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>


#define GPL_BASE 0x11000000
#define GPL2_CON GPL_BASE + 0x0100
#define GPL2_SIZE 24

#define GPK_BASE 0x11000000
#define GPK1_CON GPK_BASE + 0x0060
#define GPK1_SIZE 24

/*定义为一个数组,因为一个设备中可能有多个资源*/
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意减去1个偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
};

/*定义该方法,防止在卸载dev模块时候报警告*/
static void platform_device_test_release(struct device *dev)
{
    
}

struct platform_device led_pdev = {
    .name = "exynos4412_led",               //用作匹配,系统会在/sys/bus/platform/devices/下创建该名称文件夹
    .id = -1,                               //一般取-1,之后会详解
    .num_resources = ARRAY_SIZE(led_res),   //资源个数,此处为2
    .resource = led_res,                    //资源数组
    .dev = {
        .release = platform_device_test_release,
    },
};


static int __init plat_led_pdev_init(void)
{
    /*注册platform_device*/
    return platform_device_register(&led_pdev);
    //return platform_device_add(&led_pdev);
}

static void __exit plat_led_pdev_exit(void)
{
    platform_device_unregister(&led_pdev);
}

module_init(plat_led_pdev_init);
module_exit(plat_led_pdev_exit);
MODULE_LICENSE("GPL");
  • 执行结果
# 没有安装模块的时候没有exynos4412_led设备
[root@iTOP-4412]# ls /sys/bus/platform/devices/
...
[root@iTOP-4412]# insmod plat_led_pdev.ko
# 安装模块后出现exynos4412_led设备
[root@iTOP-4412]# ls /sys/bus/platform/devices/
11800000.fimc                    exynos4412_led
11840000.jpeg-codec              gpio-keys

2.5编写platform_driver

  • 我们需要注册一个platform_driver对象并实现相关操作代码
/*注册平台驱动*/
int platform_driver_register(struct platform_driver *drv);
/*注销平台驱动*/
void platform_driver_unregister(struct platform_driver *drv);
  • 如果该驱动与设备匹配成功则会调用驱动中的probe方法,因此我们会在probe方法里面对硬件进行操作,例如注册设备号,注册file_operation为用户提供设备标识,同时提供文件操作的接口,如read,write...
  • 填写platform_driver对象中的方法,此处需要注意id_table的填写,如果platform_driver中有id_table,则优先匹配platform_driver中的设备,否则匹配drvice中的名字(samsung led_drv),详见2.3
/*平台列表,表示本驱动可以支持的平台*/
struct platform_device_id led_id_table[] = {
    {"exynos4412_led", 0x1111},
    {"s5pv210_led", 0x2222},
    {"s3c2410_led", 0x3333},
};

struct platform_driver led_pdrv = {
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver = {
        .name = "samsung led_drv",          //如果没有定义id_table的话可以用于匹配device
    },
    .id_table = led_id_table,
};
  • platform_driver完整代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

int led_pdrv_probe(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

int led_pdrv_remove(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

/*平台列表,表示本驱动可以支持的平台*/
struct platform_device_id led_id_table[] = {
    {"exynos4412_led", 0x1111},
    {"s5pv210_led", 0x2222},
    {"s3c2410_led", 0x3333},
};

struct platform_driver led_pdrv = {
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver = {
        .name = "samsung led_drv",          //如果没有定义id_table的话可以用于匹配device
    },
    .id_table = led_id_table,
};

static int __init plat_led_pdrv_init(void)
{
    /*注册一个平台驱动*/
    platform_driver_register(&led_pdrv);
    return 0;
}

static void __exit plat_led_pdrv_exit(void)
{
    platform_driver_unregister(&led_pdrv);
}

module_init(plat_led_pdrv_init);
module_exit(plat_led_pdrv_exit);
MODULE_LICENSE("GPL");
  • 执行结果,可以看到platform_device与platform_driver匹配成功,进而执行了led_pdrv_probe方法
[root@iTOP-4412]# insmod plat_led_pdrv.ko 
[ 2071.656696] ------led_pdrv_probe------

2.6实现probe方法

  • 上述代码成功注册设备并与驱动绑定成功,那么接下来就需要在probe中实现具体的操作逻辑,其操作步骤与之前编写字符设备驱动步骤类似:动态创建主设备号,创建设备节点,硬件初始化(将物理地址映射为虚拟地址),之后再获取platform_device资源即可
  • 获取platform_device资源可通过platform_get_resource函数即可,此处需要注意获取方式,获取方式参考以下描述
/*
功能:获取设备中的资源
参数:
参数1:从哪个设备获取资源
参数2:获取的资源类型(内存资源或者中断资源)
参数3:获取同种类型的资源的第几个(注意:同种类型资源)

*/
struct resource *platform_get_resource(struct platform_device * dev, unsigned int type, unsigned int num);
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意减去1个偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = IRQ_EINT(4),
        .end = IRQ_EINT(4),
        .flags = IORESOURCE_IRQ,
    },
};

/*获取内存资源的第一个,即GPL2_CON*/
platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*获取内存资源的第二个,即GPK1_CON*/
platform_get_resource(pdev, IORESOURCE_MEM, 1);
/*获取中断资源的第一个,即IRQ_EINT(4)*/
platform_get_resource(pdev, IORESOURCE_IRQ, 0);
platform_get_irq(pdev, 0);
  • 最终我们实现的probe方法如下
int led_pdrv_probe(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    samsung_led = (struct led_dev *)kmalloc(sizeof(struct led_dev), GFP_KERNEL);
    if(samsung_led == NULL)
    {
        printk("kmalloc error\n");
        return -ENOMEM;
    }
    /*动态注册设备号*/
    samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops);
    /*创建设备节点*/
    samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
    samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0), 
                                    NULL, "led0");
    /*获取设备中寄存器资源*/
    samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    /*内存映射,硬件初始化,下述2种方法类似*/
    //ioremap(samsung_led->res->start, (samsung_led->res->end) - (samsung_led->res->start) + 1);
    samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res));
    return 0;
}
  • 之后当我们调用open函数的时候就将GPL2_0配置为输出模式,参考数据手册,我们需要将GPL2CON(32bit)寄存器的低4位(bit0~3)设置为0x1即可,设置代码如下,即先将低4位置0,再置1即可
int led_pdrv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    /*配置GPL2_0为输出*/
    writel((readl(samsung_led->reg_base) & (~((0xf)<<0))) | (0x1<<0), samsung_led->reg_base);
    return 0;
}
  • 之后在根据应用层传入的参数判断是否开关灯,此时可以看GPL2DAT寄存器,其只有8位,对于8个GPIO,对于位写0则输出0,写1则输出1.此处为GPL2_0,对应第0位,配置代码如下,需要注意的是GPL2DAT寄存器相较于基地址(GPL2CON)寄存器地址偏移了4字节,所以需要手动偏移4字节(samsung_led->reg_base + 4)
ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
    int val, ret;
    ret = copy_from_user(&val, buf, count);
    if(ret > 0)
    {
        printk("copy_from_user error\n");
        return -EFAULT;
    }
    if(val)
    {
        printk("on\n");
        /*开灯1*/
        writel(readl(samsung_led->reg_base + 4) | (0x1<<0), samsung_led->reg_base + 4);
        /*开灯2*/
        //writel(readl(samsung_led->reg_base + 4) | (0x1<<1), samsung_led->reg_base + 4);
    }
    else
    {
        printk("off\n");
        /*关灯1*/
        writel(readl(samsung_led->reg_base + 4) & ~(0x1<<0), samsung_led->reg_base + 4);
        /*关灯2*/
        //writel(readl(samsung_led->reg_base + 4) & ~(0x1<<1), samsung_led->reg_base + 4);
    }
    return 0;
}

2.7完整全部代码

  • platform_device代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>


#define GPL_BASE 0x11000000
#define GPL2_CON (GPL_BASE + 0x0100)
#define GPL2_SIZE 24

#define GPK_BASE 0x11000000
#define GPK1_CON (GPK_BASE + 0x0060)
#define GPK1_SIZE 24

/*定义为一个数组,因为一个设备中可能有多个资源*/
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意减去1个偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
};

static void platform_device_test_release(struct device *dev)
{
    
}

struct platform_device led_pdev = {
    .name = "exynos4412_led",               //用作匹配,系统会在/sys/bus/platform/devices/下创建该名称文件夹
    .id = -1,                               //一般取-1,之后会详解
    .num_resources = ARRAY_SIZE(led_res),   //资源个数,此处为2
    .resource = led_res,                    //资源数组
    .dev = {
        .release = platform_device_test_release,
    },
};


static int __init plat_led_pdev_init(void)
{
    /*注册platform_device*/
    return platform_device_register(&led_pdev);
    //return platform_device_add(&led_pdev);
}

static void __exit plat_led_pdev_exit(void)
{
    platform_device_unregister(&led_pdev);
}

module_init(plat_led_pdev_init);
module_exit(plat_led_pdev_exit);
MODULE_LICENSE("GPL");
  • platform_driver代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/ioport.h>

/*设计全局设备对象,方便后续调用*/
struct led_dev{
    int dev_major;          //主设备号
    struct class *cls;
    struct device *dev;
    struct resource *res;   //获取到的内存资源
    void *reg_base;         //保存转换后的虚拟地址
};
struct led_dev *samsung_led;

ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
    int val, ret;
    ret = copy_from_user(&val, buf, count);
    if(ret > 0)
    {
        printk("copy_from_user error\n");
        return -EFAULT;
    }
    if(val)
    {
        printk("on\n");
        /*开灯1*/
        writel(readl(samsung_led->reg_base + 4) | (0x1<<0), samsung_led->reg_base + 4);
        /*开灯2*/
        //writel(readl(samsung_led->reg_base + 4) | (0x1<<1), samsung_led->reg_base + 4);
    }
    else
    {
        printk("off\n");
        /*关灯1*/
        writel(readl(samsung_led->reg_base + 4) & ~(0x1<<0), samsung_led->reg_base + 4);
        /*关灯2*/
        //writel(readl(samsung_led->reg_base + 4) & ~(0x1<<1), samsung_led->reg_base + 4);
    }
    return 0;
}

int led_pdrv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    /*配置寄存器为输出*/
    writel((readl(samsung_led->reg_base) & (~((0xf)<<0))) | (0x1<<0), samsung_led->reg_base);
    return 0;
}

int led_pdrv_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

struct file_operations led_fops = {
    .open = led_pdrv_open,
    .release = led_pdrv_close,
    .write = led_pdrv_write,
};

int led_pdrv_probe(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    samsung_led = (struct led_dev *)kmalloc(sizeof(struct led_dev), GFP_KERNEL);
    if(samsung_led == NULL)
    {
        printk("kmalloc error\n");
        return -ENOMEM;
    }
    /*动态注册设备号*/
    samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops);
    /*创建设备节点*/
    samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
    samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0), 
                                    NULL, "led0");
    /*获取设备中寄存器资源*/
    samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    /*内存映射,硬件初始化*/
    //ioremap(samsung_led->res->start, (samsung_led->res->end) - (samsung_led->res->start) + 1);
    samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res));
    return 0;
}

int led_pdrv_remove(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    iounmap(samsung_led->reg_base);
    device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major, 0));
    class_destroy(samsung_led->cls);
    unregister_chrdev(samsung_led->dev_major, "led_drv");
    kfree(samsung_led);
    return 0;
}


/*平台列表,表示本驱动可以支持的平台*/
struct platform_device_id led_id_table[] = {
    {"exynos4412_led", 0x1111},
    {"s5pv210_led", 0x2222},
    {"s3c2410_led", 0x3333},
};


struct platform_driver led_pdrv = {
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver = {
        .name = "samsung led_drv",          //如果没有定义id_table的话可以用于匹配device
    },
    .id_table = led_id_table,
};

static int __init plat_led_pdrv_init(void)
{
    /*注册一个平台驱动*/
    platform_driver_register(&led_pdrv);
    return 0;
}

static void __exit plat_led_pdrv_exit(void)
{
    platform_driver_unregister(&led_pdrv);
}

module_init(plat_led_pdrv_init);
module_exit(plat_led_pdrv_exit);
MODULE_LICENSE("GPL");
  • 测试代码
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd;
    int led_state = 0;
    fd = open("/dev/led0", O_RDWR);
    if(fd < 0)
    {
        printf("open error\n");
        return -1;
    }
    while(1)
    {
        led_state = 1;
        write(fd, &led_state, sizeof(led_state));
        sleep(1);

        led_state = 0;
        write(fd, &led_state, sizeof(led_state));
        sleep(1);
    }
    return 0;
}
  • 现象,在安装对应模块之后,发现灯会持续亮灭
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容