繼上次研究ADC驅動,開始接觸platform這個東西。不過坦白講,還是理解的不是很透徹。所以準備今天爬一次code。以加深這方面的理解。
我們來看一個led燈的驅動是如何用平臺總線來實現的:
struct gpio_led {
const char *name;
const char *default_trigger;//觸發方式
unsigned gpio;
unsigned active_low : 1;//是高電平點亮還是低電平點亮
unsigned retain_state_suspended : 1;//休眠時是否保存狀態,等到喚醒後恢復
unsigned default_state : 2;//初始化的狀態
/* default_state should be one of LEDS_GPIO_DEFSTATE_(ON|OFF|KEEP) */
};
struct gpio_led_platform_data {
int num_leds;//led的個數
const struct gpio_led *leds;//led的結構數組
#define GPIO_LED_NO_BLINK_LOW 0 /* No blink GPIO state low */
#define GPIO_LED_NO_BLINK_HIGH 1 /* No blink GPIO state high */
#define GPIO_LED_BLINK 2 /* Please, blink */
int (*gpio_blink_set)(unsigned gpio, int state,
unsigned long *delay_on,
unsigned long *delay_off);/*閃爍的函數指針,這裏就比較像c++ 類的思想了*/
};
構造一個平臺設備,然後將設備掛載到platform的總線上。
驅動會遍歷相同總線下的所有設備,找到和本身驅動列表中name相同的設備。如果有,則綁定驅動到具體的設備,如果無,則不做任何處理。
一般情況下,設備掛載成功會在sys/bus/platform/devices 目錄下面找到你的設備
如果驅動掛載成功,會在sys/bus/platform/drivers目錄下找到你的驅動文件
那platform是一種虛擬總線,下面掛着平臺設備和平臺驅動。
1.如何構造一個平臺設備?
struct platform_device {
const char *name;//設備名稱,沒什麼好講的,一般這邊的name要和對應的驅動或者驅動列表裏面的name一致,否則驅動無法綁定成功
int id;//設備id,linux內核允許多個同名設備,同名設備間的區別用id來鑑別,PLATFORM_DEVID_NONE(-1)
PLATFORM_DEVID_AUTO(-2)
bool id_auto;// 初始化此flag,可以讓內核自動分配id
struct device dev;//設備結構體,platform_device是由這個結構體抽象來的
u32 num_resources;//資源數
struct resource *resource;//資源列表,2者同時出現,用於描述資源,一般包括I/O、Memory、Register、IRQ、DMA、Bus等多種類型
const struct platform_device_id *id_entry;//平臺設備列表
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
我們粗略看完了platform_device 這個結構體。然後繼續分析它裏面的一些結構體:
device結構體
/**
* struct device - The basic device structure
* @parent: The device's "parent" device, the device to which it is attached.
* In most cases, a parent device is some sort of bus or host
* controller. If parent is NULL, the device, is a top-level device,
* which is not usually what you want.
* @p: Holds the private data of the driver core portions of the device.
* See the comment of the struct device_private for detail.
* @kobj: A top-level, abstract class from which other classes are derived.
* @init_name: Initial name of the device.
* @type: The type of device.
* This identifies the device type and carries type-specific
* information.
* @mutex: Mutex to synchronize calls to its driver.
* @bus: Type of bus device is on.
* @driver: Which driver has allocated this
* @platform_data: Platform data specific to the device.
* Example: For devices on custom boards, as typical of embedded
* and SOC based hardware, Linux often uses platform_data to point
* to board-specific structures describing devices and how they
* are wired. That can include what ports are available, chip
* variants, which GPIO pins act in what additional roles, and so
* on. This shrinks the "Board Support Packages" (BSPs) and
* minimizes board-specific #ifdefs in drivers.
* @power: For device power management.
* See Documentation/power/devices.txt for details.
* @pm_domain: Provide callbacks that are executed during system suspend,
* hibernation, system resume and during runtime PM transitions
* along with subsystem-level and driver-level callbacks.
* @pins: For device pin management.
* See Documentation/pinctrl.txt for details.
* @numa_node: NUMA node this device is close to.
* @dma_mask: Dma mask (if dma'ble device).
* @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
* hardware supports 64-bit addresses for consistent allocations
* such descriptors.
* @dma_parms: A low level driver may set these to teach IOMMU code about
* segment limitations.
* @dma_pools: Dma pools (if dma'ble device).
* @dma_mem: Internal for coherent mem override.
* @archdata: For arch-specific additions.
* @of_node: Associated device tree node.
* @acpi_node: Associated ACPI device node.
* @devt: For creating the sysfs "dev".
* @id: device instance
* @devres_lock: Spinlock to protect the resource of the device.
* @devres_head: The resources list of the device.
* @knode_class: The node used to add the device to the class list.
* @class: The class of the device.
* @groups: Optional attribute groups.
* @release: Callback to free the device after all references have
* gone away. This should be set by the allocator of the
* device (i.e. the bus driver that discovered the device).
*
* At the lowest level, every device in a Linux system is represented by an
* instance of struct device. The device structure contains the information
* that the device model core needs to model the system. Most subsystems,
* however, track additional information about the devices they host. As a
* result, it is rare for devices to be represented by bare device structures;
* instead, that structure, like kobject structures, is usually embedded within
* a higher-level representation of the 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 */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
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. */
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_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 acpi_dev_node acpi_node; /* associated ACPI 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);
struct iommu_group *iommu_group;
}
我們不再深入研究這個結構體,第一次,因爲它上面本身就有大概的說明,第二我們只是研究平臺設備,所以不便深入研究這個,可能說以後有機會再深入看看吧。
resource:
/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start;//描述設備實體在cpu總線上的線性起始物理地址
resource_size_t end;//描述設備實體在cpu總線上的線性結尾物理地址
const char *name;
unsigned long flags;//描述這個設備實體的一些共性和特性的標誌位
struct resource *parent, *sibling, *child;//分別爲指向父親、兄弟和子資源的指針
};
platform_device_id:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];//.name
kernel_ulong_t driver_data;//驅動數據
};
其中:typedef unsigned long kernel_ulong_t;
後面兩個結構體暫時不做了解,目前用不到:mfd_cell 和 pdev_archdata。
那麼這裏我們已經把一個platform_device 分析的差不多了。下來構造一個led平臺設備:
static struct platform_device smdkv210_leds ={
.name = "leds-gpio",
.dev ={
.platform_data = &smdkv210_leds_pdata,
},
.id = -1,//-1代表PLATFORM_DEVID_NONE
};
static struct gpio_led_platform_data smdkv210_leds_pdata ={
.num_leds = ARRAY_SIZE(leds),
.leds = leds,
};
static struct gpio_led leds[] ={
[0] ={
.name ="led0",
.gpio = S5PV210_GPJ2(0),
.active_low = 1,
.default_state = LEDS_GPIO_DEFSTATE_OFF,
},
[1] ={
.name ="led1",
.gpio = S5PV210_GPJ2(1),
.active_low = 1,
.default_state = LEDS_GPIO_DEFSTATE_OFF,
},
};
其中:void *platform_data;
這邊一個平臺設備定義成功。接下來,我們看看平臺設備是如何被添加進到平臺設備總線下面的:
platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));
/**
* platform_add_devices - add a numbers of platform devices
* @devs: array of platform devices to add
* @num: number of platform devices in array
*/
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
這邊添加一組平臺設備到平臺總線。
platform_add_devices這個函式沒有什麼好解釋的,註冊所有的平臺設備,註冊失敗的話就註銷這個設備。
全部註冊成功,返回0.
接下來:
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);//初始化平臺設備下面的dev結構體
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);//將平臺設備添加到平臺設備列表中,並返回操作結果
}
接下面分別分析這其中的兩個函數,中間那個函數先不看,用於初始化平臺設備的archdata結構體
/**
* device_initialize - init device structure.
* @dev: device.
*
* This prepares the device for use by other layers by initializing
* its fields.
* It is the first half of device_register(), if called by
* that function, though it can also be called separately, so one
* may use @dev's fields. In particular, get_device()/put_device()
* may be used for reference counting of @dev after calling this
* function.
*
* All fields in @dev must be initialized by the caller to 0, except
* for those explicitly set to some other value. The simplest
* approach is to use kzalloc() to allocate the structure containing
* @dev.
*
* NOTE: Use put_device() to give up your reference instead of freeing
* @dev directly once you have called this function.
*/
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
這邊先不作研究,能力有限,下次再更新
/**
* platform_device_add - add a platform device to device hierarchy
* @pdev: platform device we're adding
*
* This is part 2 of platform_device_register(), though may be called
* separately _iff_ pdev was allocated by platform_device_alloc().
*/
int platform_device_add(struct platform_device *pdev)
{
int i, ret;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;//如果設備無父節點,那就直接掛在platform的總線上
pdev->dev.bus = &platform_bus_type;//總線類型爲platform總線
switch (pdev->id) {
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);//用平臺設備的name,id參數填充平臺設備的dev結構體
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name);//只填充name,不填充ID
break;
case PLATFORM_DEVID_AUTO:
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);//自動獲取ID,並填充name和id到dev結構體
break;
}
/*將資源列表放到資源樹中進行管理*/
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;//p指向平臺設備資源的父設備,如果父設備不爲空,
則通過平臺資源的資源類型flag來獲取P(父設備資源列表)的指針
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)//資源類型爲memory
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)//資源類型爲IO資源
p = &ioport_resource;
}
//如果P爲空,把平臺設備的資源添加到資源樹中進行管理。
//如果P不爲空,將平臺設備的資源列表和資源列表父設備的資源列表一起加入到資源樹中管理
if (p && insert_resource(p, r)) {
dev_err(&pdev->dev, "failed to claim resource %d\n", i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret = device_add(&pdev->dev);//添加到設備樹中去
if (ret == 0)
return ret;
failed:
if (pdev->id_auto) {
ida_simple_remove(&platform_devid_ida, pdev->id);
pdev->id = PLATFORM_DEVID_AUTO;
}
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
err_out:
return ret;
}
其中此函數中的幾個函數原型:
/**
* dev_set_name - set a device name
* @dev: device
* @fmt: format string for the device's name
*/
int dev_set_name(struct device *dev, const char *fmt, ...)
{
va_list vargs;
int err;
va_start(vargs, fmt);
err = kobject_set_name_vargs(&dev->kobj, fmt, vargs);
va_end(vargs);
return err;
}
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
struct resource ioport_resource = {
.name = "PCI IO",
.start = 0,
.end = IO_SPACE_LIMIT,
.flags = IORESOURCE_IO,
};
/**
* insert_resource - Inserts a resource in the resource tree
* @parent: parent of the new resource
* @new: new resource to insert
*
* Returns 0 on success, -EBUSY if the resource can't be inserted.
*/
int insert_resource(struct resource *parent, struct resource *new)
{
struct resource *conflict;
conflict = insert_resource_conflict(parent, new);
return conflict ? -EBUSY : 0;
}
先說一下resource擴展出的兩顆樹,一個針對於IO,一個針對對mem。
在Linux內核中,訪問外設上的IO Port必須通過IO Port的尋址方式。而訪問IO Mem就比較羅嗦,外部MEM不能和主存一樣訪問,因爲外部MEM是沒有在系統中註冊的。訪問外部IO MEM必須通過remap映射到內核的MEM空間後才能訪問。爲了達到接口的同一性,內核提供了IO Port到IO Mem的映射函數。映射後IO Port就可以看作是IO Mem,按照IO Mem的訪問方式即可。
再來,設備的添加:
/**
* device_add - add device to device hierarchy.
* @dev: device.
*
* This is part 2 of device_register(), though may be called
* separately _iff_ device_initialize() has been called separately.
*
* This adds @dev to the kobject hierarchy via kobject_add(), adds it
* to the global and sibling lists for the device, then
* adds it to the other relevant subsystems of the driver model.
*
* Do not call this routine or device_register() more than once for
* any device structure. The driver model core is not designed to work
* with devices that get unregistered and then spring back to life.
* (Among other things, it's very hard to guarantee that all references
* to the previous incarnation of @dev have been dropped.) Allocate
* and register a fresh new struct device instead.
*
* NOTE: _Never_ directly free @dev after calling this function, even
* if it returned an error! Always use put_device() to give up your
* reference instead.
*/
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);//initial the name dev->kobj->name=dev->init_name
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
//initial the name dev->kobj->name=dev->bus->dev_name
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
//get the dev->parent's dev pointer
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);//get dev->parent->kobj
//if dev->parent->kobj exist, initial the dev->kobj->parent=dev->parent->kobj
if (kobj)
dev->kobj.parent = kobj;
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);/*kobj arg set*/
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
//device_create_file - create sysfs attribute file for device.uevent attr
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
//device_create_file - create sysfs attribute file for device.devt attr
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);//dev entry
if (error)
goto devtattrError;
devtmpfs_create_node(dev);//dev node
}
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);//probe device
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
devtmpfs_delete_node(dev);
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
坦白講,這邊的內核看的懂很難很難,中秋看了好久其實也看不太懂。還是在看了許多相關視頻的基礎上。
但不管怎樣,學習還是要繼續下去。雖然內核大部分的代碼都是用C寫的,可是裏面已經有了c++的思想。我自認爲c語言學的不是很好,雖然現在有許多語言,可是一門c都學不精,又何必貪多呢!可以這樣講,如果你能把內核代碼理解個七七八八,我覺得還是不愁喫喝的,而且待遇不會低。
這邊我就先不更了,只能說以後有能力了,再深入去理解這些東西。
LED平臺設備的添加:
/*LED*/
static struct gpio_led leds[] ={
[0] ={
.name ="led0",
.default_trigger = "heartbeat",
.gpio = S5PV210_GPJ2(0),
.active_low = 1,
.default_state = LEDS_GPIO_DEFSTATE_OFF,
},
[1] ={
.name = "led1",
.gpio = S5PV210_GPJ2(1),
.active_low = 1,
.default_state = LEDS_GPIO_DEFSTATE_OFF,
},
[2] ={
.name = "led2",
.gpio = S5PV210_GPJ2(2),
.active_low = 1,
.default_state = LEDS_GPIO_DEFSTATE_OFF,
},
[3] ={
.name = "led3",
.gpio = S5PV210_GPJ2(3),
.active_low = 1,
.default_state = LEDS_GPIO_DEFSTATE_OFF,
},
};
static struct gpio_led_platform_data smdkv210_leds_pdata ={
.num_leds = ARRAY_SIZE(leds),
.leds = leds,
};
static struct platform_device smdkv210_leds ={
.name = "leds-gpio",
.dev ={
.platform_data = &smdkv210_leds_pdata,
},
.id = -1,
};
這邊構造出平臺設備,然後把 smdkv210_leds加入到平臺設備列表中。當然最後要打開led 驅動的支持。
配置內核:Devices drivers–>LED support–> LED class support
–>LED Support for GPIO connected LEDs
make uImage
重新利用uboot 載入內核,當進入到 busybox配置好的NFS文件系統後會有:
平臺設備添加直觀上看就是把掛載到同一總線下的驅動和設備綁定成功。
實際的效果是分別在某些目錄下生成了對應的設備,驅動目錄。裏面又有具體的屬性…
當然這篇文章可能只能先這樣了,等到時機成熟,有能力時再做筆記。