Linux Kernel driver model

来源:互联网 发布:淘宝店铺装修购买模板 编辑:程序博客网 时间:2024/04/25 08:11

Kernel driver model

Peter xu, 2012.07

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Agenda

What is device/driver register?

Platform device-driver model

I2C device-driver model

Device/Driver register

There are three key entities including bus, device, driver.

Declare there is one special device/driver on the BUS

The name and ID of device/driver must be exclusive

The process of register is only to add the device/driver kobject into the kobject list(kset), not verify whether are the details right or not.

The bus driver will match the device and driver according to name or id.

One device must have only one driver; one driver may have more devices

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform device-driver model

Platform bus

Platform device

Platform driver

Platform device&driver case

Register ab8500_i2c device&driver

Register ab8500_fg device&driver

Register power_supply class and ab8500_fg device

Create capacity attribute

Register path

sys/devices/platform/xxxx

Mapping path: sys/bus/platform/devices/xxxx

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform Bus

Bus is the channel between processor and devices.

New driver management and register from Linux 2.6.

This pseudo-bus is used to connect devices on busses with minimal infrastructure, like those used to integrate peripherals on many system-on-chip processors.

More easily plant

Important struct @kernel/drivers/base/platform.c

struct bus_type platform_bus_type = {

                .name   = "platform",

                .dev_attrs = platform_dev_attrs,

                .match  = platform_match, //compare device name with driver name for attaching together

                .uevent                = platform_uevent,

                .pm        = &platform_dev_pm_ops,

};

struct device platform_bus = {

                .init_name          = "platform",

};

struct bus_type_private {

                struct kset subsys;

                struct kset *drivers_kset;  // the list of drivers associated with this bus

                struct kset *devices_kset; // the list of devices associated with this bus

                struct klist klist_devices; // the klist to iterate over the @devices_kset

                struct klist klist_drivers; //the klist to iterate over the @drivers_kset

                struct blocking_notifier_head bus_notifier;

                unsigned int drivers_autoprobe:1;

                struct bus_type *bus;

};

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform Bus (cont’)

Important function @kernel/drivers/base/platform.c

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);

                /* match against the id table first */

                if (pdrv->id_table) //in general ,null

                                return platform_match_id(pdrv->id_table, pdev) != NULL;

                /* fall-back to driver name match */

                return (strcmp(pdev->name, drv->name) == 0); //compare names

}

int __init platform_bus_init(void)

                {…

                error =  bus_register(&platform_bus_type); //register platform bus, /sys/bus/platform

}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform device

Platform devices are devices that typically appear as autonomous entities in the system. This includes legacy port-based devices and host bridges to peripheral buses, and most controllers integrated into system-on-chip platforms. What they usually have in common is direct addressing from a CPU bus. Rarely, a platform device will be connected through a segment of some other kind of bus; but its registers will still be directly addressable.

Platform devices are given a name, used in driver binding, and a list of resources such as addresses and IRQs.

struct platform_device { //special device, derive from device

   const char *name; //the name must be same with  the name of counterpart driver

   u32 id;

   struct device dev;

   u32 num_resources;

   struct resource *resource; //CPU HW resource, such as MEM,IO,IRQ,DMA

};

int platform_device_register(struct platform_device *pdev)

int platform_device_add(struct platform_device *pdev){

                pdev->dev.bus = &platform_bus_type;

}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform device (cont’)

@drivers/base/platform.c

int platform_device_register(struct platform_device *pdev){

                device_initialize(&pdev->dev); //init device structure

                return platform_device_add(pdev);

}

int platform_device_add(struct platform_device *pdev)

                {…

                if (!pdev->dev.parent)

                                pdev->dev.parent = &platform_bus; //platform node

                pdev->dev.bus = &platform_bus_type; //platform bus type

                …

                ret = device_add(&pdev->dev); //add a platform device to device hierarchy

}

int __init platform_bus_init(void)

                {…

                error = device_register(&platform_bus); //register platform device, /sys/devices/platform

}

static int platform_match(struct device *dev, struct device_driver *drv)

                {…

                return (strcmp(pdev->name, drv->name) == 0); //compare device name and driver name

}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform device (cont’)

device_add(struct device *dev)@kernel/drivers/base/core.c

àbus_probe_device(dev) @kernel/drivers/base/bus.c

àdevice_attach(dev)@kernel/drivers/base/dd.c

                àbus_for_each_drv(dev->bus, NULL, dev, __device_attach) //compare all drivers in bus to the device

à__device_attach@ kernel/drivers/base/bus.c

àdriver_match_device@kernel/drivers/base/base.h

                àdrv->bus->match(dev,drv)    

àplatform_match@kernel/drivers/base/platform.c //compare driver name and device name

find the matched driver

àdriver_probe_device(drv, dev) @kernel/drivers/base

àreally_probe(dev, drv) @kernel/drivers/base

{…

                if (dev->bus->probe) { //=null

                                ret = dev->bus->probe(dev);

                                if (ret)

                                                goto probe_failed;

                } else if (drv->probe) { //call platform driver probe function

                                ret = drv->probe(dev);

                                if (ret)

                                                goto probe_failed;

                }

                …

}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform driver

Platform drivers follow the standard driver model convention, where discovery/enumeration is handled outside the drivers, and drivers provide probe() and remove() methods. They support power management and shutdown notifications using the standard conventions.

struct platform_driver {//special driver, derive from device_driver

int (*probe)(struct platform_device *);

int (*remove)(struct platform_device *);

void (*shutdown)(struct platform_device *);

int (*suspend)(struct platform_device *, pm_message_t state);

int (*suspend_late)(struct platform_device *, pm_message_t state);

int (*resume_early)(struct platform_device *);

int (*resume)(struct platform_device *);

struct device_driver driver;

};

Platform drivers register themselves the normal way

int platform_driver_register(struct platform_driver *drv);

{

                drv->driver.bus = &platform_bus_type;

                if (drv->probe)

                                drv->driver.probe = platform_drv_probe;

                if (drv->remove)

                                drv->driver.remove = platform_drv_remove;

                if (drv->shutdown)

                                drv->driver.shutdown = platform_drv_shutdown;

                return driver_register(&drv->driver);

}

In common situations where the device is known not to be hot-pluggable,  the probe() routine can live in an init section to reduce the driver's runtime memory footprint:

int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *))

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform driver (cont’)

driver_register(&drv->driver) @kernel/drivers/base/driver.c

àbus_add_driver(drv) @kernel/drivers/base/bus.c

àdriver_attach(drv) @kernel/drivers/base/dd.c

àbus_for_each_dev(drv->bus, NULL, drv, __driver_attach); //compare all devices in bus to the driver

à__driver_attach@kernel/drivers/base/dd.c

àdriver_match_device(drv, dev) @kernel/drivers/base/base.h

                àdrv->bus->match(dev,drv)    

àplatform_match@kernel/drivers/base/platform.c //compare driver name and device name

find the matched device

àdriver_probe_device(drv, dev) @kernel/drivers/base

àreally_probe(dev, drv) @kernel/drivers/base

{…

                if (dev->bus->probe) { //=null

                                ret = dev->bus->probe(dev);

                                if (ret)

                                                goto probe_failed;

                } else if (drv->probe) { //call platform driver probe function

                                ret = drv->probe(dev);

                                if (ret)

                                                goto probe_failed;

                }

                …

}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform device case: ab8500_i2c on Riogrande

@kernel/arch/arm/mach-ux500/board-rio-grande.c

Platform device structure

static struct platform_device ux500_ab8500_device = {

                .name = "ab8500-i2c",

                .id = 0,

                .dev = {

                                .platform_data = &ab8500_platdata,

                },

                .num_resources = 1,

                .resource = ab8500_resources,

};

Platform device resource

static struct resource ab8500_resources[] = {

                [0] = {

                                .start = IRQ_DB8500_AB8500,

                                .end = IRQ_DB8500_AB8500,

                                .flags = IORESOURCE_IRQ //interrupt resource

                }

};

Register the “ab8500-i2c” device platform

platform_device_register(&ux500_ab8500_device);//@kernel/drivers/base/platform.c

platform_device_add(…){

                …

                if (!pdev->dev.parent)

                                pdev->dev.parent = &platform_bus;

}

The “ab8500-i2c” device  has been registered  under the node of “platform”

sys/devices/platform/ab8500-i2c.0

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform driver case: ab8500_i2c on Riogrande

@kernel/drivers/mfd/ab8500-i2c.c

Platform driver structure

static struct platform_driver ab8500_i2c_driver = {

                .driver = {

                                .name = "ab8500-i2c",

                                .owner = THIS_MODULE,

                },

                .probe  = ab8500_i2c_probe,

                .remove               = __devexit_p(ab8500_i2c_remove)

};

Register the “ab8500-i2c” driver platform

platform_driver_register(&ab8500_i2c_driver);

Run probe

ab8500_i2c_probe

ab8500_init(ab8500);@kernel/drivers/mfd/ab8500-core.c

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform device case: ab8500_fg on Riogrande

@kernel/drivers/mfd/ab8500-core.c

Platform device structure

mfd: multifunction device drivers

struct mfd_cell@kernel/linux/mfd/core.h

static struct mfd_cell __devinitdata ab8500_bm_devs[] = 

                                ...

                                {

                                   .name = "ab8500-fg",

                                   .num_resources = ARRAY_SIZE(ab8500_fg_resources),

                                   .resources = ab8500_fg_resources,

                                }

Platform resource

static struct resource __devinitdata ab8500_fg_resources[] = {

                {

                                .name = "NCONV_ACCU",

                                .start = AB8500_INT_CCN_CONV_ACC,

                                .end = AB8500_INT_CCN_CONV_ACC,

                                .flags = IORESOURCE_IRQ,  //interrupt resource

                },

Register the “ab8500-fg” device platform

mfd_add_devices(ab8500->dev..,ab8500_bm_devs,…); // @kernel/drivers/mfd/mfd-core.c

platform_device_add(..); // @kernel/driver/base/platform.c

The “ab8500-fg” device has been registered  under the node of “ab8500-i2c”

sys/devices/platform/ab8500-i2c.0/ab8500-fg.0

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Platform driver case: power_supply class on Riogrande

@kernel/drivers/power/power_supply_core.c

static int __init power_supply_class_init(void)

{

                power_supply_class = class_create(THIS_MODULE, "power_supply");

                if (IS_ERR(power_supply_class))

                                return PTR_ERR(power_supply_class);

                power_supply_class->dev_uevent = power_supply_uevent;

                power_supply_init_attrs(&power_supply_dev_type);

                return 0;

}

power_supply_register(struct device *parent, struct power_supply *psy)

                {

dev->class = power_supply_class;

dev->parent = parent; //the node of  “sys/devices/platform/ab8500-i2c.0/ab8500-fg.0”

rc = kobject_set_name(&dev->kobj, "%s", psy->name); //name = “ab8500_fg”

rc = device_add(dev);

                 }

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Platform driver case: ab8500_fg on Riogrande

@kernel/drivers/power/ab8500_fg.c

Platform driver structure

static struct platform_driver ab8500_fg_driver = {

                .probe = ab8500_fg_probe,

                .remove = __devexit_p(ab8500_fg_remove),

                .suspend = ab8500_fg_suspend,

                .resume = ab8500_fg_resume,

                .driver = {

                                .name = "ab8500-fg",

                                .owner = THIS_MODULE,

                },

};

Register the “ab8500-fg” driver platform

platform_driver_register(&ab8500_fg_driver);

Run probe function

Init HW parameters

static int __devinit ab8500_fg_probe(struct platform_device *pdev) {

di->fg_psy.name = "ab8500_fg";

ret = power_supply_register(di->dev, &di->fg_psy);

..

}

power_supply_register@kernel/drivers/power/power_supply_core.c

The “power_supply/ab8500_fg” device has been registered  under the node of “ab8500-fg.0”

sys/devices/platform/ab8500-i2c.0/ab8500-fg.0/power_supply/ab8500_fg

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Platform driver attribute case: ab8500_fg on Riogrande

@kernel/drivers/power/ab8500_fg.c

static int __devinit ab8500_fg_probe(struct platform_device *pdev)

{

                di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY;

                di->fg_psy.properties = ab8500_fg_props;

                di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props);

                di->fg_psy.get_property = ab8500_fg_get_property;

       ret = ab8500_fg_sysfs_psy_create_attrs(di->fg_psy.dev);

}

static struct device_attribute ab8500_fg_sysfs_psy_attrs[] = {

                __ATTR(capacity, S_IRUGO, show_capacity, NULL),

};

static enum power_supply_property ab8500_fg_props[] = {

                POWER_SUPPLY_PROP_VOLTAGE_NOW,

                POWER_SUPPLY_PROP_CURRENT_NOW,

                POWER_SUPPLY_PROP_CURRENT_AVG,

                POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,

                POWER_SUPPLY_PROP_ENERGY_FULL,

                POWER_SUPPLY_PROP_ENERGY_NOW,

                POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,

                POWER_SUPPLY_PROP_CHARGE_FULL,

                POWER_SUPPLY_PROP_CHARGE_NOW,

                POWER_SUPPLY_PROP_CAPACITY_LEVEL,

};

The attributes of capacity, voltage_now, etc have been created under the node of “ab8500_fg”

sys/devices/platform/ab8500-i2c.0/ab8500-fg.0/power_supply/ab8500_fg/capacity

Platform register case workflow chart:


sys/devices/platform/ab8500-i2c.0/ab8500-fg.0/power_supply/ab8500_fg/capacity

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------I2CI2C device-driver model

I2C device-driver model architecture

I2C device-driver case

I2C device case “nmk-i2c”  on Riogrande

I2C device adapter&client case on Riogrande

I2C driver case “as3677” on Riogrande

I2C device-driver model

I2C is the name for a two-wire serial bus protocol(SDA,SCL) originally developed by Phillips. It commonly is used in embedded systems so different components can communicate;

The I2C kernel code is broken up into a number of logical pieces: the I2C core, I2C bus drivers, I2C algorithm drivers and I2C chip drivers.

I2C Core

I2C Bus Drivers

An I2C bus driver is described by a struct named i2c_adapter

I2C Algorithm Drivers

An I2C algorithm is used by the I2C bus driver to talk to the I2C bus. Most I2C bus drivers define their own I2C algorithms and use them, as they are tied closely to how the bus driver talks to that specific type of hardware.

I2C Chip Drivers

Register Path:

/sys/devices/platform/

Mapping path: /sys/bus/i2c/devices/xxxx

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------I2CI2C device-driver model architecture

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------I2CI2C device case “nmk-i2c”  on Riogrande

@kernel/arch/arm/mach-ux500/board-rio-grande.c

i2c_board_info

static struct i2c_board_info __initdata pdp_i2c2_devices[] =

{

                                I2C_BOARD_INFO("as3677", 0x80 >> 1), //i2c name=“as3677”,address=0x40

                                .platform_data = &as3677_pdata,

                                .irq = 92,

}

pdp_i2c_init 

db8500_add_i2c2(&pdp_i2c2_data)  ,there are four I2C buses on DB8500

dbx500_add_i2c(2, U8500_I2C2_BASE, IRQ_DB8500_I2C2, pdata)

dbx500_add_platform_device_4k1irq("nmk-i2c", id, base, irq, pdata);

{

                struct resource resources[] = {

                                [0] = { //4k memory map

                                                .start     = base,

                                                .end       = base + SZ_4K - 1,

                                                .flags     = IORESOURCE_MEM,

                                },

                                [1] = { //interrupt IRQ_DB8500_I2C2

                                                .start     = irq,

                                                .end       = irq,

                                                .flags     = IORESOURCE_IRQ,

                                }

                };

                return dbx500_add_platform_device(name, id, pdata, resources,  ARRAY_SIZE(resources));

}

platform_device_add: add device “nmk-i2c” under platform node

i2c_register_board_info(2, ARRAY_AND_SIZE(pdp_i2c2_devices));

Add pdp_i2c2_devices into __i2c_board_list, busnum=2 (I2CBUS)

Register I2C device

 /sys/devices/platform/nmk-i2c.2/

I2C driver case “nmk-i2c” on Riogrande

@kernel/drivers/i2c/busses/i2c-nomadik.c

Platform driver struct

static struct platform_driver nmk_i2c_driver = {

                .driver = {

                                .owner = THIS_MODULE,

                                .name = DRIVER_NAME, //name="nmk-i2c"

                                .pm = &nmk_i2c_pm,

                },

                .probe = nmk_i2c_probe,

                .remove = __devexit_p(nmk_i2c_remove),

};

platform_driver_register(&nmk_i2c_driver);

Find the I2C device by name of “nmk-i2c”

Register I2C driver

/sys/devices/platform/nmk-i2c.2

I2C device adapter&client case on Riogrande

Run probe function: nmk_i2c_probe@kernel/drivers/i2c/busses/i2c-nomadik.c

adap->nr = pdev->id; //=2

Add adapter

i2c_add_numbered_adapter(adap)@kernel/drivers/i2c/i2c-core.c

i2c_register_adapter(adap)@kernel/drivers/i2c/i2c-core.c

dev_set_name(&adap->dev, "i2c-%d", adap->nr); //name=”i2c-2”

adap->dev.bus = &i2c_bus_type;

device_register(&adap->dev);

Register I2C adapter device

/sys/devices/platform/nmk-i2c.2/i2c-2

i2c_scan_static_board_info(adap)

Compare __i2c_board_list.id and adap.nr,One I2C bus device must be matched one I2C adapter

i2c_new_device

client->dev.bus = &i2c_bus_type;

client->dev.type = &i2c_client_type;

dev_set_name(&client->dev, “%d-%04x”, i2c_adapter_id(adap),client->addr); //I2C BUS ID=2, I2C device address=0x40 for “as3677”

device_register(&client->dev);

Register I2C client device

                 /sys/devices/platform/nmk-i2c.2/i2c-2/2-0040

I2C driver case “as3677” on Riogrande

@kernel/drivers/leds/led-class.c

leds_init : create class

leds_class = class_create(THIS_MODULE, "leds");

led_classdev_register //register a new object of the class

@kernel/drivers/leds/leds-as3677.c

I2C driver struct

static struct i2c_driver as3677_driver = {

                .driver = {

                                .name   = "as3677",

#ifdef CONFIG_PM

                                .pm     = &as3677_pm,

#endif

                },

                .probe  = as3677_probe,

                .remove = as3677_remove,

                .shutdown = as3677_shutdown,

                .id_table = as3677_id,

};

 i2c_add_driver(&as3677_driver);à i2c_register_driver(THIS_MODULE, driver);

driver_register(&drv->driver)@kernel/drivers/base/driver.c

bus_add_driver@kernel/drivers/base/bus.c //Add the driver into i2c_bus_type I2C BUS(adapter)

bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver); //attach the driver to matched adapter

as3677_probe

as3677_configure(client, data, as3677_pdata)

led_classdev_register(&client->dev, &led->ldev);

Register  “3677” led device

/sys/devices/platform/nmk-i2c.2/i2c-2/2-0040/leds

device_add_attributes(led->ldev.dev,as3677_led_attributes); //add attribute

/sys/devices/platform/nmk-i2c.2/i2c-2/2-0040/leds/lcd-backlight