Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
149
drivers/base/Kconfig
Normal file
149
drivers/base/Kconfig
Normal file
@@ -0,0 +1,149 @@
|
||||
menu "Generic Driver Options"
|
||||
|
||||
# Qisda, Asaku Chen {
|
||||
config QISDA_QD060B00
|
||||
bool "QISDA QD060B00"
|
||||
default n
|
||||
help
|
||||
Say Y here, and a list of supported qisda_e600 will be displayed.
|
||||
This option doesn't affect the kernel.
|
||||
|
||||
choice
|
||||
depends on QISDA_QD060B00
|
||||
prompt "QISDA_QD060B00"
|
||||
default QISDA_QD060N00_DVT1_1
|
||||
|
||||
config QISDA_QD060N00_DVT1_1
|
||||
bool "DVT1_1"
|
||||
---help---
|
||||
QISDA_QD060N00_DVT1_1
|
||||
|
||||
config QISDA_E600_DVT1
|
||||
bool "DVT1"
|
||||
---help---
|
||||
QISDA_E600_DVT1
|
||||
|
||||
config QISDA_E600_EVT2
|
||||
bool "EVT2"
|
||||
---help---
|
||||
QISDA_E600_EVT2
|
||||
|
||||
config QISDA_E600_EVT0
|
||||
bool "EVT0"
|
||||
---help---
|
||||
QISDA_E600_EVT0
|
||||
|
||||
endchoice
|
||||
# Qisda, Asaku Chen }
|
||||
|
||||
# Qisda, Asaku Chen {
|
||||
config QISDA_QD090B00
|
||||
bool "QISDA QD090B00"
|
||||
default n
|
||||
help
|
||||
Say Y here, and a list of supported QD090B00 will be displayed.
|
||||
This option doesn't affect the kernel.
|
||||
|
||||
choice
|
||||
depends on QISDA_QD090B00
|
||||
prompt "QISDA_QD090B00"
|
||||
default QISDA_QD090B00_EVT1
|
||||
config QISDA_QD090B00_EVT1
|
||||
bool "EVT1"
|
||||
---help---
|
||||
QISDA_QD090B00_EVT1
|
||||
|
||||
endchoice
|
||||
# Qisda, Asaku Chen }
|
||||
|
||||
|
||||
# Qisda, Tim Huang {
|
||||
config QISDA_AS090B00
|
||||
bool "QISDA AS090B00"
|
||||
default n
|
||||
help
|
||||
Say Y here, and a list of supported model AS090B00 will be displayed.
|
||||
This option doesn't affect the kernel.
|
||||
|
||||
choice
|
||||
depends on QISDA_AS090B00
|
||||
prompt "qisda as090b00"
|
||||
default QISDA_AS090B00_EVT1
|
||||
config QISDA_AS090B00_EVT1
|
||||
bool "EVT1"
|
||||
---help---
|
||||
QISDA_AS090B00_EVT1
|
||||
|
||||
config QISDA_AS090B00_EVT1_1
|
||||
bool "EVT1_1"
|
||||
---help---
|
||||
QISDA_AS090B00_EVT1_1
|
||||
|
||||
endchoice
|
||||
# Qisda, Tim Huang }
|
||||
|
||||
#Qisda Qube{
|
||||
|
||||
config QISDA_BADBLOCK_CHECK
|
||||
bool "QISDA_BADBLOCK_CHECK"
|
||||
default n
|
||||
help
|
||||
QISDA_BADBLOCK_CHECK
|
||||
|
||||
#Qisda Qube}
|
||||
|
||||
|
||||
config STANDALONE
|
||||
bool "Select only drivers that don't need compile-time external firmware" if EXPERIMENTAL
|
||||
default y
|
||||
help
|
||||
Select this option if you don't have magic firmware for drivers that
|
||||
need it.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config PREVENT_FIRMWARE_BUILD
|
||||
bool "Prevent firmware from being built"
|
||||
default y
|
||||
help
|
||||
Say yes to avoid building firmware. Firmware is usually shipped
|
||||
with the driver, and only when updating the firmware a rebuild
|
||||
should be made.
|
||||
If unsure say Y here.
|
||||
|
||||
config FW_LOADER
|
||||
tristate "Userspace firmware loading support"
|
||||
depends on HOTPLUG
|
||||
---help---
|
||||
This option is provided for the case where no in-kernel-tree modules
|
||||
require userspace firmware loading support, but a module built outside
|
||||
the kernel tree does.
|
||||
|
||||
config DEBUG_DRIVER
|
||||
bool "Driver Core verbose debug messages"
|
||||
depends on DEBUG_KERNEL
|
||||
help
|
||||
Say Y here if you want the Driver core to produce a bunch of
|
||||
debug messages to the system log. Select this if you are having a
|
||||
problem with the driver core and want to see more of what is
|
||||
going on.
|
||||
|
||||
If you are unsure about this, say N here.
|
||||
|
||||
config DEBUG_DEVRES
|
||||
bool "Managed device resources verbose debug messages"
|
||||
depends on DEBUG_KERNEL
|
||||
help
|
||||
This option enables kernel parameter devres.log. If set to
|
||||
non-zero, devres debug messages are printed. Select this if
|
||||
you are having a problem with devres or want to debug
|
||||
resource management for a managed device. devres.log can be
|
||||
switched on and off from sysfs node.
|
||||
|
||||
If you are unsure about this, Say N here.
|
||||
|
||||
config SYS_HYPERVISOR
|
||||
bool
|
||||
default n
|
||||
|
||||
endmenu
|
||||
19
drivers/base/Makefile
Normal file
19
drivers/base/Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
# Makefile for the Linux device tree
|
||||
|
||||
obj-y := core.o sys.o bus.o dd.o \
|
||||
driver.o class.o platform.o \
|
||||
cpu.o firmware.o init.o map.o dmapool.o \
|
||||
dma-mapping.o devres.o \
|
||||
attribute_container.o transport_class.o
|
||||
obj-y += power/
|
||||
obj-$(CONFIG_ISA) += isa.o
|
||||
obj-$(CONFIG_FW_LOADER) += firmware_class.o
|
||||
obj-$(CONFIG_NUMA) += node.o
|
||||
obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o
|
||||
obj-$(CONFIG_SMP) += topology.o
|
||||
obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o
|
||||
|
||||
ifeq ($(CONFIG_DEBUG_DRIVER),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
437
drivers/base/attribute_container.c
Normal file
437
drivers/base/attribute_container.c
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* attribute_container.c - implementation of a simple container for classes
|
||||
*
|
||||
* Copyright (c) 2005 - James Bottomley <James.Bottomley@steeleye.com>
|
||||
*
|
||||
* This file is licensed under GPLv2
|
||||
*
|
||||
* The basic idea here is to enable a device to be attached to an
|
||||
* aritrary numer of classes without having to allocate storage for them.
|
||||
* Instead, the contained classes select the devices they need to attach
|
||||
* to via a matching function.
|
||||
*/
|
||||
|
||||
#include <linux/attribute_container.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
/* This is a private structure used to tie the classdev and the
|
||||
* container .. it should never be visible outside this file */
|
||||
struct internal_container {
|
||||
struct klist_node node;
|
||||
struct attribute_container *cont;
|
||||
struct class_device classdev;
|
||||
};
|
||||
|
||||
static void internal_container_klist_get(struct klist_node *n)
|
||||
{
|
||||
struct internal_container *ic =
|
||||
container_of(n, struct internal_container, node);
|
||||
class_device_get(&ic->classdev);
|
||||
}
|
||||
|
||||
static void internal_container_klist_put(struct klist_node *n)
|
||||
{
|
||||
struct internal_container *ic =
|
||||
container_of(n, struct internal_container, node);
|
||||
class_device_put(&ic->classdev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* attribute_container_classdev_to_container - given a classdev, return the container
|
||||
*
|
||||
* @classdev: the class device created by attribute_container_add_device.
|
||||
*
|
||||
* Returns the container associated with this classdev.
|
||||
*/
|
||||
struct attribute_container *
|
||||
attribute_container_classdev_to_container(struct class_device *classdev)
|
||||
{
|
||||
struct internal_container *ic =
|
||||
container_of(classdev, struct internal_container, classdev);
|
||||
return ic->cont;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(attribute_container_classdev_to_container);
|
||||
|
||||
static struct list_head attribute_container_list;
|
||||
|
||||
static DECLARE_MUTEX(attribute_container_mutex);
|
||||
|
||||
/**
|
||||
* attribute_container_register - register an attribute container
|
||||
*
|
||||
* @cont: The container to register. This must be allocated by the
|
||||
* callee and should also be zeroed by it.
|
||||
*/
|
||||
int
|
||||
attribute_container_register(struct attribute_container *cont)
|
||||
{
|
||||
INIT_LIST_HEAD(&cont->node);
|
||||
klist_init(&cont->containers,internal_container_klist_get,
|
||||
internal_container_klist_put);
|
||||
|
||||
down(&attribute_container_mutex);
|
||||
list_add_tail(&cont->node, &attribute_container_list);
|
||||
up(&attribute_container_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(attribute_container_register);
|
||||
|
||||
/**
|
||||
* attribute_container_unregister - remove a container registration
|
||||
*
|
||||
* @cont: previously registered container to remove
|
||||
*/
|
||||
int
|
||||
attribute_container_unregister(struct attribute_container *cont)
|
||||
{
|
||||
int retval = -EBUSY;
|
||||
down(&attribute_container_mutex);
|
||||
spin_lock(&cont->containers.k_lock);
|
||||
if (!list_empty(&cont->containers.k_list))
|
||||
goto out;
|
||||
retval = 0;
|
||||
list_del(&cont->node);
|
||||
out:
|
||||
spin_unlock(&cont->containers.k_lock);
|
||||
up(&attribute_container_mutex);
|
||||
return retval;
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(attribute_container_unregister);
|
||||
|
||||
/* private function used as class release */
|
||||
static void attribute_container_release(struct class_device *classdev)
|
||||
{
|
||||
struct internal_container *ic
|
||||
= container_of(classdev, struct internal_container, classdev);
|
||||
struct device *dev = classdev->dev;
|
||||
|
||||
kfree(ic);
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_add_device - see if any container is interested in dev
|
||||
*
|
||||
* @dev: device to add attributes to
|
||||
* @fn: function to trigger addition of class device.
|
||||
*
|
||||
* This function allocates storage for the class device(s) to be
|
||||
* attached to dev (one for each matching attribute_container). If no
|
||||
* fn is provided, the code will simply register the class device via
|
||||
* class_device_add. If a function is provided, it is expected to add
|
||||
* the class device at the appropriate time. One of the things that
|
||||
* might be necessary is to allocate and initialise the classdev and
|
||||
* then add it a later time. To do this, call this routine for
|
||||
* allocation and initialisation and then use
|
||||
* attribute_container_device_trigger() to call class_device_add() on
|
||||
* it. Note: after this, the class device contains a reference to dev
|
||||
* which is not relinquished until the release of the classdev.
|
||||
*/
|
||||
void
|
||||
attribute_container_add_device(struct device *dev,
|
||||
int (*fn)(struct attribute_container *,
|
||||
struct device *,
|
||||
struct class_device *))
|
||||
{
|
||||
struct attribute_container *cont;
|
||||
|
||||
down(&attribute_container_mutex);
|
||||
list_for_each_entry(cont, &attribute_container_list, node) {
|
||||
struct internal_container *ic;
|
||||
|
||||
if (attribute_container_no_classdevs(cont))
|
||||
continue;
|
||||
|
||||
if (!cont->match(cont, dev))
|
||||
continue;
|
||||
|
||||
ic = kzalloc(sizeof(*ic), GFP_KERNEL);
|
||||
if (!ic) {
|
||||
dev_printk(KERN_ERR, dev, "failed to allocate class container\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
ic->cont = cont;
|
||||
class_device_initialize(&ic->classdev);
|
||||
ic->classdev.dev = get_device(dev);
|
||||
ic->classdev.class = cont->class;
|
||||
cont->class->release = attribute_container_release;
|
||||
strcpy(ic->classdev.class_id, dev->bus_id);
|
||||
if (fn)
|
||||
fn(cont, dev, &ic->classdev);
|
||||
else
|
||||
attribute_container_add_class_device(&ic->classdev);
|
||||
klist_add_tail(&ic->node, &cont->containers);
|
||||
}
|
||||
up(&attribute_container_mutex);
|
||||
}
|
||||
|
||||
/* FIXME: can't break out of this unless klist_iter_exit is also
|
||||
* called before doing the break
|
||||
*/
|
||||
#define klist_for_each_entry(pos, head, member, iter) \
|
||||
for (klist_iter_init(head, iter); (pos = ({ \
|
||||
struct klist_node *n = klist_next(iter); \
|
||||
n ? container_of(n, typeof(*pos), member) : \
|
||||
({ klist_iter_exit(iter) ; NULL; }); \
|
||||
}) ) != NULL; )
|
||||
|
||||
|
||||
/**
|
||||
* attribute_container_remove_device - make device eligible for removal.
|
||||
*
|
||||
* @dev: The generic device
|
||||
* @fn: A function to call to remove the device
|
||||
*
|
||||
* This routine triggers device removal. If fn is NULL, then it is
|
||||
* simply done via class_device_unregister (note that if something
|
||||
* still has a reference to the classdev, then the memory occupied
|
||||
* will not be freed until the classdev is released). If you want a
|
||||
* two phase release: remove from visibility and then delete the
|
||||
* device, then you should use this routine with a fn that calls
|
||||
* class_device_del() and then use
|
||||
* attribute_container_device_trigger() to do the final put on the
|
||||
* classdev.
|
||||
*/
|
||||
void
|
||||
attribute_container_remove_device(struct device *dev,
|
||||
void (*fn)(struct attribute_container *,
|
||||
struct device *,
|
||||
struct class_device *))
|
||||
{
|
||||
struct attribute_container *cont;
|
||||
|
||||
down(&attribute_container_mutex);
|
||||
list_for_each_entry(cont, &attribute_container_list, node) {
|
||||
struct internal_container *ic;
|
||||
struct klist_iter iter;
|
||||
|
||||
if (attribute_container_no_classdevs(cont))
|
||||
continue;
|
||||
|
||||
if (!cont->match(cont, dev))
|
||||
continue;
|
||||
|
||||
klist_for_each_entry(ic, &cont->containers, node, &iter) {
|
||||
if (dev != ic->classdev.dev)
|
||||
continue;
|
||||
klist_del(&ic->node);
|
||||
if (fn)
|
||||
fn(cont, dev, &ic->classdev);
|
||||
else {
|
||||
attribute_container_remove_attrs(&ic->classdev);
|
||||
class_device_unregister(&ic->classdev);
|
||||
}
|
||||
}
|
||||
}
|
||||
up(&attribute_container_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_device_trigger - execute a trigger for each matching classdev
|
||||
*
|
||||
* @dev: The generic device to run the trigger for
|
||||
* @fn the function to execute for each classdev.
|
||||
*
|
||||
* This funcion is for executing a trigger when you need to know both
|
||||
* the container and the classdev. If you only care about the
|
||||
* container, then use attribute_container_trigger() instead.
|
||||
*/
|
||||
void
|
||||
attribute_container_device_trigger(struct device *dev,
|
||||
int (*fn)(struct attribute_container *,
|
||||
struct device *,
|
||||
struct class_device *))
|
||||
{
|
||||
struct attribute_container *cont;
|
||||
|
||||
down(&attribute_container_mutex);
|
||||
list_for_each_entry(cont, &attribute_container_list, node) {
|
||||
struct internal_container *ic;
|
||||
struct klist_iter iter;
|
||||
|
||||
if (!cont->match(cont, dev))
|
||||
continue;
|
||||
|
||||
if (attribute_container_no_classdevs(cont)) {
|
||||
fn(cont, dev, NULL);
|
||||
continue;
|
||||
}
|
||||
|
||||
klist_for_each_entry(ic, &cont->containers, node, &iter) {
|
||||
if (dev == ic->classdev.dev)
|
||||
fn(cont, dev, &ic->classdev);
|
||||
}
|
||||
}
|
||||
up(&attribute_container_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_trigger - trigger a function for each matching container
|
||||
*
|
||||
* @dev: The generic device to activate the trigger for
|
||||
* @fn: the function to trigger
|
||||
*
|
||||
* This routine triggers a function that only needs to know the
|
||||
* matching containers (not the classdev) associated with a device.
|
||||
* It is more lightweight than attribute_container_device_trigger, so
|
||||
* should be used in preference unless the triggering function
|
||||
* actually needs to know the classdev.
|
||||
*/
|
||||
void
|
||||
attribute_container_trigger(struct device *dev,
|
||||
int (*fn)(struct attribute_container *,
|
||||
struct device *))
|
||||
{
|
||||
struct attribute_container *cont;
|
||||
|
||||
down(&attribute_container_mutex);
|
||||
list_for_each_entry(cont, &attribute_container_list, node) {
|
||||
if (cont->match(cont, dev))
|
||||
fn(cont, dev);
|
||||
}
|
||||
up(&attribute_container_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_add_attrs - add attributes
|
||||
*
|
||||
* @classdev: The class device
|
||||
*
|
||||
* This simply creates all the class device sysfs files from the
|
||||
* attributes listed in the container
|
||||
*/
|
||||
int
|
||||
attribute_container_add_attrs(struct class_device *classdev)
|
||||
{
|
||||
struct attribute_container *cont =
|
||||
attribute_container_classdev_to_container(classdev);
|
||||
struct class_device_attribute **attrs = cont->attrs;
|
||||
int i, error;
|
||||
|
||||
if (!attrs)
|
||||
return 0;
|
||||
|
||||
for (i = 0; attrs[i]; i++) {
|
||||
error = class_device_create_file(classdev, attrs[i]);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_add_class_device - same function as class_device_add
|
||||
*
|
||||
* @classdev: the class device to add
|
||||
*
|
||||
* This performs essentially the same function as class_device_add except for
|
||||
* attribute containers, namely add the classdev to the system and then
|
||||
* create the attribute files
|
||||
*/
|
||||
int
|
||||
attribute_container_add_class_device(struct class_device *classdev)
|
||||
{
|
||||
int error = class_device_add(classdev);
|
||||
if (error)
|
||||
return error;
|
||||
return attribute_container_add_attrs(classdev);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_add_class_device_adapter - simple adapter for triggers
|
||||
*
|
||||
* This function is identical to attribute_container_add_class_device except
|
||||
* that it is designed to be called from the triggers
|
||||
*/
|
||||
int
|
||||
attribute_container_add_class_device_adapter(struct attribute_container *cont,
|
||||
struct device *dev,
|
||||
struct class_device *classdev)
|
||||
{
|
||||
return attribute_container_add_class_device(classdev);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_remove_attrs - remove any attribute files
|
||||
*
|
||||
* @classdev: The class device to remove the files from
|
||||
*
|
||||
*/
|
||||
void
|
||||
attribute_container_remove_attrs(struct class_device *classdev)
|
||||
{
|
||||
struct attribute_container *cont =
|
||||
attribute_container_classdev_to_container(classdev);
|
||||
struct class_device_attribute **attrs = cont->attrs;
|
||||
int i;
|
||||
|
||||
if (!attrs)
|
||||
return;
|
||||
|
||||
for (i = 0; attrs[i]; i++)
|
||||
class_device_remove_file(classdev, attrs[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_class_device_del - equivalent of class_device_del
|
||||
*
|
||||
* @classdev: the class device
|
||||
*
|
||||
* This function simply removes all the attribute files and then calls
|
||||
* class_device_del.
|
||||
*/
|
||||
void
|
||||
attribute_container_class_device_del(struct class_device *classdev)
|
||||
{
|
||||
attribute_container_remove_attrs(classdev);
|
||||
class_device_del(classdev);
|
||||
}
|
||||
|
||||
/**
|
||||
* attribute_container_find_class_device - find the corresponding class_device
|
||||
*
|
||||
* @cont: the container
|
||||
* @dev: the generic device
|
||||
*
|
||||
* Looks up the device in the container's list of class devices and returns
|
||||
* the corresponding class_device.
|
||||
*/
|
||||
struct class_device *
|
||||
attribute_container_find_class_device(struct attribute_container *cont,
|
||||
struct device *dev)
|
||||
{
|
||||
struct class_device *cdev = NULL;
|
||||
struct internal_container *ic;
|
||||
struct klist_iter iter;
|
||||
|
||||
klist_for_each_entry(ic, &cont->containers, node, &iter) {
|
||||
if (ic->classdev.dev == dev) {
|
||||
cdev = &ic->classdev;
|
||||
/* FIXME: must exit iterator then break */
|
||||
klist_iter_exit(&iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cdev;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(attribute_container_find_class_device);
|
||||
|
||||
int __init
|
||||
attribute_container_init(void)
|
||||
{
|
||||
INIT_LIST_HEAD(&attribute_container_list);
|
||||
return 0;
|
||||
}
|
||||
47
drivers/base/base.h
Normal file
47
drivers/base/base.h
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
/* initialisation functions */
|
||||
|
||||
extern int devices_init(void);
|
||||
extern int buses_init(void);
|
||||
extern int classes_init(void);
|
||||
extern int firmware_init(void);
|
||||
#ifdef CONFIG_SYS_HYPERVISOR
|
||||
extern int hypervisor_init(void);
|
||||
#else
|
||||
static inline int hypervisor_init(void) { return 0; }
|
||||
#endif
|
||||
extern int platform_bus_init(void);
|
||||
extern int system_bus_init(void);
|
||||
extern int cpu_dev_init(void);
|
||||
extern int attribute_container_init(void);
|
||||
|
||||
extern int bus_add_device(struct device * dev);
|
||||
extern int bus_attach_device(struct device * dev);
|
||||
extern void bus_remove_device(struct device * dev);
|
||||
extern struct bus_type *get_bus(struct bus_type * bus);
|
||||
extern void put_bus(struct bus_type * bus);
|
||||
|
||||
extern int bus_add_driver(struct device_driver *);
|
||||
extern void bus_remove_driver(struct device_driver *);
|
||||
|
||||
extern void driver_detach(struct device_driver * drv);
|
||||
extern int driver_probe_device(struct device_driver *, struct device *);
|
||||
|
||||
extern void sysdev_shutdown(void);
|
||||
extern int sysdev_suspend(pm_message_t state);
|
||||
extern int sysdev_resume(void);
|
||||
|
||||
static inline struct class_device *to_class_dev(struct kobject *obj)
|
||||
{
|
||||
return container_of(obj, struct class_device, kobj);
|
||||
}
|
||||
|
||||
static inline
|
||||
struct class_device_attribute *to_class_dev_attr(struct attribute *_attr)
|
||||
{
|
||||
return container_of(_attr, struct class_device_attribute, attr);
|
||||
}
|
||||
|
||||
extern char *make_class_name(const char *name, struct kobject *kobj);
|
||||
|
||||
extern void devres_release_all(struct device *dev);
|
||||
826
drivers/base/bus.c
Normal file
826
drivers/base/bus.c
Normal file
@@ -0,0 +1,826 @@
|
||||
/*
|
||||
* bus.c - bus driver management
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* Copyright (c) 2002-3 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/string.h>
|
||||
#include "base.h"
|
||||
#include "power/power.h"
|
||||
|
||||
#define to_bus_attr(_attr) container_of(_attr, struct bus_attribute, attr)
|
||||
#define to_bus(obj) container_of(obj, struct bus_type, subsys.kset.kobj)
|
||||
|
||||
/*
|
||||
* sysfs bindings for drivers
|
||||
*/
|
||||
|
||||
#define to_drv_attr(_attr) container_of(_attr, struct driver_attribute, attr)
|
||||
#define to_driver(obj) container_of(obj, struct device_driver, kobj)
|
||||
|
||||
|
||||
static ssize_t
|
||||
drv_attr_show(struct kobject * kobj, struct attribute * attr, char * buf)
|
||||
{
|
||||
struct driver_attribute * drv_attr = to_drv_attr(attr);
|
||||
struct device_driver * drv = to_driver(kobj);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (drv_attr->show)
|
||||
ret = drv_attr->show(drv, buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
drv_attr_store(struct kobject * kobj, struct attribute * attr,
|
||||
const char * buf, size_t count)
|
||||
{
|
||||
struct driver_attribute * drv_attr = to_drv_attr(attr);
|
||||
struct device_driver * drv = to_driver(kobj);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (drv_attr->store)
|
||||
ret = drv_attr->store(drv, buf, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sysfs_ops driver_sysfs_ops = {
|
||||
.show = drv_attr_show,
|
||||
.store = drv_attr_store,
|
||||
};
|
||||
|
||||
|
||||
static void driver_release(struct kobject * kobj)
|
||||
{
|
||||
struct device_driver * drv = to_driver(kobj);
|
||||
complete(&drv->unloaded);
|
||||
}
|
||||
|
||||
static struct kobj_type ktype_driver = {
|
||||
.sysfs_ops = &driver_sysfs_ops,
|
||||
.release = driver_release,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* sysfs bindings for buses
|
||||
*/
|
||||
|
||||
|
||||
static ssize_t
|
||||
bus_attr_show(struct kobject * kobj, struct attribute * attr, char * buf)
|
||||
{
|
||||
struct bus_attribute * bus_attr = to_bus_attr(attr);
|
||||
struct bus_type * bus = to_bus(kobj);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (bus_attr->show)
|
||||
ret = bus_attr->show(bus, buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
bus_attr_store(struct kobject * kobj, struct attribute * attr,
|
||||
const char * buf, size_t count)
|
||||
{
|
||||
struct bus_attribute * bus_attr = to_bus_attr(attr);
|
||||
struct bus_type * bus = to_bus(kobj);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (bus_attr->store)
|
||||
ret = bus_attr->store(bus, buf, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sysfs_ops bus_sysfs_ops = {
|
||||
.show = bus_attr_show,
|
||||
.store = bus_attr_store,
|
||||
};
|
||||
|
||||
int bus_create_file(struct bus_type * bus, struct bus_attribute * attr)
|
||||
{
|
||||
int error;
|
||||
if (get_bus(bus)) {
|
||||
error = sysfs_create_file(&bus->subsys.kset.kobj, &attr->attr);
|
||||
put_bus(bus);
|
||||
} else
|
||||
error = -EINVAL;
|
||||
return error;
|
||||
}
|
||||
|
||||
void bus_remove_file(struct bus_type * bus, struct bus_attribute * attr)
|
||||
{
|
||||
if (get_bus(bus)) {
|
||||
sysfs_remove_file(&bus->subsys.kset.kobj, &attr->attr);
|
||||
put_bus(bus);
|
||||
}
|
||||
}
|
||||
|
||||
static struct kobj_type ktype_bus = {
|
||||
.sysfs_ops = &bus_sysfs_ops,
|
||||
|
||||
};
|
||||
|
||||
static decl_subsys(bus, &ktype_bus, NULL);
|
||||
|
||||
|
||||
#ifdef CONFIG_HOTPLUG
|
||||
|
||||
/* Manually detach a device from its associated driver. */
|
||||
static int driver_helper(struct device *dev, void *data)
|
||||
{
|
||||
const char *name = data;
|
||||
|
||||
if (strcmp(name, dev->bus_id) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t driver_unbind(struct device_driver *drv,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct bus_type *bus = get_bus(drv->bus);
|
||||
struct device *dev;
|
||||
int err = -ENODEV;
|
||||
|
||||
dev = bus_find_device(bus, NULL, (void *)buf, driver_helper);
|
||||
if (dev && dev->driver == drv) {
|
||||
if (dev->parent) /* Needed for USB */
|
||||
down(&dev->parent->sem);
|
||||
device_release_driver(dev);
|
||||
if (dev->parent)
|
||||
up(&dev->parent->sem);
|
||||
err = count;
|
||||
}
|
||||
put_device(dev);
|
||||
put_bus(bus);
|
||||
return err;
|
||||
}
|
||||
static DRIVER_ATTR(unbind, S_IWUSR, NULL, driver_unbind);
|
||||
|
||||
/*
|
||||
* Manually attach a device to a driver.
|
||||
* Note: the driver must want to bind to the device,
|
||||
* it is not possible to override the driver's id table.
|
||||
*/
|
||||
static ssize_t driver_bind(struct device_driver *drv,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct bus_type *bus = get_bus(drv->bus);
|
||||
struct device *dev;
|
||||
int err = -ENODEV;
|
||||
|
||||
dev = bus_find_device(bus, NULL, (void *)buf, driver_helper);
|
||||
if (dev && dev->driver == NULL) {
|
||||
if (dev->parent) /* Needed for USB */
|
||||
down(&dev->parent->sem);
|
||||
down(&dev->sem);
|
||||
err = driver_probe_device(drv, dev);
|
||||
up(&dev->sem);
|
||||
if (dev->parent)
|
||||
up(&dev->parent->sem);
|
||||
|
||||
if (err > 0) /* success */
|
||||
err = count;
|
||||
else if (err == 0) /* driver didn't accept device */
|
||||
err = -ENODEV;
|
||||
}
|
||||
put_device(dev);
|
||||
put_bus(bus);
|
||||
return err;
|
||||
}
|
||||
static DRIVER_ATTR(bind, S_IWUSR, NULL, driver_bind);
|
||||
|
||||
#endif
|
||||
|
||||
static struct device * next_device(struct klist_iter * i)
|
||||
{
|
||||
struct klist_node * n = klist_next(i);
|
||||
return n ? container_of(n, struct device, knode_bus) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_for_each_dev - device iterator.
|
||||
* @bus: bus type.
|
||||
* @start: device to start iterating from.
|
||||
* @data: data for the callback.
|
||||
* @fn: function to be called for each device.
|
||||
*
|
||||
* Iterate over @bus's list of devices, and call @fn for each,
|
||||
* passing it @data. If @start is not NULL, we use that device to
|
||||
* begin iterating from.
|
||||
*
|
||||
* We check the return of @fn each time. If it returns anything
|
||||
* other than 0, we break out and return that value.
|
||||
*
|
||||
* NOTE: The device that returns a non-zero value is not retained
|
||||
* in any way, nor is its refcount incremented. If the caller needs
|
||||
* to retain this data, it should do, and increment the reference
|
||||
* count in the supplied callback.
|
||||
*/
|
||||
|
||||
int bus_for_each_dev(struct bus_type * bus, struct device * start,
|
||||
void * data, int (*fn)(struct device *, void *))
|
||||
{
|
||||
struct klist_iter i;
|
||||
struct device * dev;
|
||||
int error = 0;
|
||||
|
||||
if (!bus)
|
||||
return -EINVAL;
|
||||
|
||||
klist_iter_init_node(&bus->klist_devices, &i,
|
||||
(start ? &start->knode_bus : NULL));
|
||||
while ((dev = next_device(&i)) && !error)
|
||||
error = fn(dev, data);
|
||||
klist_iter_exit(&i);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_find_device - device iterator for locating a particular device.
|
||||
* @bus: bus type
|
||||
* @start: Device to begin with
|
||||
* @data: Data to pass to match function
|
||||
* @match: Callback function to check device
|
||||
*
|
||||
* This is similar to the bus_for_each_dev() function above, but it
|
||||
* returns a reference to a device that is 'found' for later use, as
|
||||
* determined by the @match callback.
|
||||
*
|
||||
* The callback should return 0 if the device doesn't match and non-zero
|
||||
* if it does. If the callback returns non-zero, this function will
|
||||
* return to the caller and not iterate over any more devices.
|
||||
*/
|
||||
struct device * bus_find_device(struct bus_type *bus,
|
||||
struct device *start, void *data,
|
||||
int (*match)(struct device *, void *))
|
||||
{
|
||||
struct klist_iter i;
|
||||
struct device *dev;
|
||||
|
||||
if (!bus)
|
||||
return NULL;
|
||||
|
||||
klist_iter_init_node(&bus->klist_devices, &i,
|
||||
(start ? &start->knode_bus : NULL));
|
||||
while ((dev = next_device(&i)))
|
||||
if (match(dev, data) && get_device(dev))
|
||||
break;
|
||||
klist_iter_exit(&i);
|
||||
return dev;
|
||||
}
|
||||
|
||||
|
||||
static struct device_driver * next_driver(struct klist_iter * i)
|
||||
{
|
||||
struct klist_node * n = klist_next(i);
|
||||
return n ? container_of(n, struct device_driver, knode_bus) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_for_each_drv - driver iterator
|
||||
* @bus: bus we're dealing with.
|
||||
* @start: driver to start iterating on.
|
||||
* @data: data to pass to the callback.
|
||||
* @fn: function to call for each driver.
|
||||
*
|
||||
* This is nearly identical to the device iterator above.
|
||||
* We iterate over each driver that belongs to @bus, and call
|
||||
* @fn for each. If @fn returns anything but 0, we break out
|
||||
* and return it. If @start is not NULL, we use it as the head
|
||||
* of the list.
|
||||
*
|
||||
* NOTE: we don't return the driver that returns a non-zero
|
||||
* value, nor do we leave the reference count incremented for that
|
||||
* driver. If the caller needs to know that info, it must set it
|
||||
* in the callback. It must also be sure to increment the refcount
|
||||
* so it doesn't disappear before returning to the caller.
|
||||
*/
|
||||
|
||||
int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
|
||||
void * data, int (*fn)(struct device_driver *, void *))
|
||||
{
|
||||
struct klist_iter i;
|
||||
struct device_driver * drv;
|
||||
int error = 0;
|
||||
|
||||
if (!bus)
|
||||
return -EINVAL;
|
||||
|
||||
klist_iter_init_node(&bus->klist_drivers, &i,
|
||||
start ? &start->knode_bus : NULL);
|
||||
while ((drv = next_driver(&i)) && !error)
|
||||
error = fn(drv, data);
|
||||
klist_iter_exit(&i);
|
||||
return error;
|
||||
}
|
||||
|
||||
static int device_add_attrs(struct bus_type *bus, struct device *dev)
|
||||
{
|
||||
int error = 0;
|
||||
int i;
|
||||
|
||||
if (!bus->dev_attrs)
|
||||
return 0;
|
||||
|
||||
for (i = 0; attr_name(bus->dev_attrs[i]); i++) {
|
||||
error = device_create_file(dev,&bus->dev_attrs[i]);
|
||||
if (error) {
|
||||
while (--i >= 0)
|
||||
device_remove_file(dev, &bus->dev_attrs[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static void device_remove_attrs(struct bus_type * bus, struct device * dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (bus->dev_attrs) {
|
||||
for (i = 0; attr_name(bus->dev_attrs[i]); i++)
|
||||
device_remove_file(dev,&bus->dev_attrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SYSFS_DEPRECATED
|
||||
static int make_deprecated_bus_links(struct device *dev)
|
||||
{
|
||||
return sysfs_create_link(&dev->kobj,
|
||||
&dev->bus->subsys.kset.kobj, "bus");
|
||||
}
|
||||
|
||||
static void remove_deprecated_bus_links(struct device *dev)
|
||||
{
|
||||
sysfs_remove_link(&dev->kobj, "bus");
|
||||
}
|
||||
#else
|
||||
static inline int make_deprecated_bus_links(struct device *dev) { return 0; }
|
||||
static inline void remove_deprecated_bus_links(struct device *dev) { }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* bus_add_device - add device to bus
|
||||
* @dev: device being added
|
||||
*
|
||||
* - Add the device to its bus's list of devices.
|
||||
* - Create link to device's bus.
|
||||
*/
|
||||
int bus_add_device(struct device * dev)
|
||||
{
|
||||
struct bus_type * bus = get_bus(dev->bus);
|
||||
int error = 0;
|
||||
|
||||
if (bus) {
|
||||
pr_debug("bus %s: add device %s\n", bus->name, dev->bus_id);
|
||||
error = device_add_attrs(bus, dev);
|
||||
if (error)
|
||||
goto out_put;
|
||||
error = sysfs_create_link(&bus->devices.kobj,
|
||||
&dev->kobj, dev->bus_id);
|
||||
if (error)
|
||||
goto out_id;
|
||||
error = sysfs_create_link(&dev->kobj,
|
||||
&dev->bus->subsys.kset.kobj, "subsystem");
|
||||
if (error)
|
||||
goto out_subsys;
|
||||
error = make_deprecated_bus_links(dev);
|
||||
if (error)
|
||||
goto out_deprecated;
|
||||
}
|
||||
return 0;
|
||||
|
||||
out_deprecated:
|
||||
sysfs_remove_link(&dev->kobj, "subsystem");
|
||||
out_subsys:
|
||||
sysfs_remove_link(&bus->devices.kobj, dev->bus_id);
|
||||
out_id:
|
||||
device_remove_attrs(bus, dev);
|
||||
out_put:
|
||||
put_bus(dev->bus);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_attach_device - add device to bus
|
||||
* @dev: device tried to attach to a driver
|
||||
*
|
||||
* - Add device to bus's list of devices.
|
||||
* - Try to attach to driver.
|
||||
*/
|
||||
int bus_attach_device(struct device * dev)
|
||||
{
|
||||
struct bus_type *bus = dev->bus;
|
||||
int ret = 0;
|
||||
|
||||
if (bus) {
|
||||
dev->is_registered = 1;
|
||||
ret = device_attach(dev);
|
||||
if (ret >= 0) {
|
||||
klist_add_tail(&dev->knode_bus, &bus->klist_devices);
|
||||
ret = 0;
|
||||
} else
|
||||
dev->is_registered = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_remove_device - remove device from bus
|
||||
* @dev: device to be removed
|
||||
*
|
||||
* - Remove symlink from bus's directory.
|
||||
* - Delete device from bus's list.
|
||||
* - Detach from its driver.
|
||||
* - Drop reference taken in bus_add_device().
|
||||
*/
|
||||
void bus_remove_device(struct device * dev)
|
||||
{
|
||||
if (dev->bus) {
|
||||
sysfs_remove_link(&dev->kobj, "subsystem");
|
||||
remove_deprecated_bus_links(dev);
|
||||
sysfs_remove_link(&dev->bus->devices.kobj, dev->bus_id);
|
||||
device_remove_attrs(dev->bus, dev);
|
||||
if (dev->is_registered) {
|
||||
dev->is_registered = 0;
|
||||
klist_del(&dev->knode_bus);
|
||||
}
|
||||
pr_debug("bus %s: remove device %s\n", dev->bus->name, dev->bus_id);
|
||||
device_release_driver(dev);
|
||||
put_bus(dev->bus);
|
||||
}
|
||||
}
|
||||
|
||||
static int driver_add_attrs(struct bus_type * bus, struct device_driver * drv)
|
||||
{
|
||||
int error = 0;
|
||||
int i;
|
||||
|
||||
if (bus->drv_attrs) {
|
||||
for (i = 0; attr_name(bus->drv_attrs[i]); i++) {
|
||||
error = driver_create_file(drv, &bus->drv_attrs[i]);
|
||||
if (error)
|
||||
goto Err;
|
||||
}
|
||||
}
|
||||
Done:
|
||||
return error;
|
||||
Err:
|
||||
while (--i >= 0)
|
||||
driver_remove_file(drv, &bus->drv_attrs[i]);
|
||||
goto Done;
|
||||
}
|
||||
|
||||
|
||||
static void driver_remove_attrs(struct bus_type * bus, struct device_driver * drv)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (bus->drv_attrs) {
|
||||
for (i = 0; attr_name(bus->drv_attrs[i]); i++)
|
||||
driver_remove_file(drv, &bus->drv_attrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG
|
||||
/*
|
||||
* Thanks to drivers making their tables __devinit, we can't allow manual
|
||||
* bind and unbind from userspace unless CONFIG_HOTPLUG is enabled.
|
||||
*/
|
||||
static int __must_check add_bind_files(struct device_driver *drv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = driver_create_file(drv, &driver_attr_unbind);
|
||||
if (ret == 0) {
|
||||
ret = driver_create_file(drv, &driver_attr_bind);
|
||||
if (ret)
|
||||
driver_remove_file(drv, &driver_attr_unbind);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void remove_bind_files(struct device_driver *drv)
|
||||
{
|
||||
driver_remove_file(drv, &driver_attr_bind);
|
||||
driver_remove_file(drv, &driver_attr_unbind);
|
||||
}
|
||||
#else
|
||||
static inline int add_bind_files(struct device_driver *drv) { return 0; }
|
||||
static inline void remove_bind_files(struct device_driver *drv) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* bus_add_driver - Add a driver to the bus.
|
||||
* @drv: driver.
|
||||
*
|
||||
*/
|
||||
int bus_add_driver(struct device_driver *drv)
|
||||
{
|
||||
struct bus_type * bus = get_bus(drv->bus);
|
||||
int error = 0;
|
||||
|
||||
if (!bus)
|
||||
return 0;
|
||||
|
||||
pr_debug("bus %s: add driver %s\n", bus->name, drv->name);
|
||||
error = kobject_set_name(&drv->kobj, "%s", drv->name);
|
||||
if (error)
|
||||
goto out_put_bus;
|
||||
drv->kobj.kset = &bus->drivers;
|
||||
if ((error = kobject_register(&drv->kobj)))
|
||||
goto out_put_bus;
|
||||
|
||||
error = driver_attach(drv);
|
||||
if (error)
|
||||
goto out_unregister;
|
||||
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
|
||||
module_add_driver(drv->owner, drv);
|
||||
|
||||
error = driver_add_attrs(bus, drv);
|
||||
if (error) {
|
||||
/* How the hell do we get out of this pickle? Give up */
|
||||
printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
|
||||
__FUNCTION__, drv->name);
|
||||
}
|
||||
error = add_bind_files(drv);
|
||||
if (error) {
|
||||
/* Ditto */
|
||||
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
|
||||
__FUNCTION__, drv->name);
|
||||
}
|
||||
|
||||
return error;
|
||||
out_unregister:
|
||||
kobject_unregister(&drv->kobj);
|
||||
out_put_bus:
|
||||
put_bus(bus);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_remove_driver - delete driver from bus's knowledge.
|
||||
* @drv: driver.
|
||||
*
|
||||
* Detach the driver from the devices it controls, and remove
|
||||
* it from its bus's list of drivers. Finally, we drop the reference
|
||||
* to the bus we took in bus_add_driver().
|
||||
*/
|
||||
|
||||
void bus_remove_driver(struct device_driver * drv)
|
||||
{
|
||||
if (!drv->bus)
|
||||
return;
|
||||
|
||||
remove_bind_files(drv);
|
||||
driver_remove_attrs(drv->bus, drv);
|
||||
klist_remove(&drv->knode_bus);
|
||||
pr_debug("bus %s: remove driver %s\n", drv->bus->name, drv->name);
|
||||
driver_detach(drv);
|
||||
module_remove_driver(drv);
|
||||
kobject_unregister(&drv->kobj);
|
||||
put_bus(drv->bus);
|
||||
}
|
||||
|
||||
|
||||
/* Helper for bus_rescan_devices's iter */
|
||||
static int __must_check bus_rescan_devices_helper(struct device *dev,
|
||||
void *data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!dev->driver) {
|
||||
if (dev->parent) /* Needed for USB */
|
||||
down(&dev->parent->sem);
|
||||
ret = device_attach(dev);
|
||||
if (dev->parent)
|
||||
up(&dev->parent->sem);
|
||||
if (ret > 0)
|
||||
ret = 0;
|
||||
}
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_rescan_devices - rescan devices on the bus for possible drivers
|
||||
* @bus: the bus to scan.
|
||||
*
|
||||
* This function will look for devices on the bus with no driver
|
||||
* attached and rescan it against existing drivers to see if it matches
|
||||
* any by calling device_attach() for the unbound devices.
|
||||
*/
|
||||
int bus_rescan_devices(struct bus_type * bus)
|
||||
{
|
||||
return bus_for_each_dev(bus, NULL, NULL, bus_rescan_devices_helper);
|
||||
}
|
||||
|
||||
/**
|
||||
* device_reprobe - remove driver for a device and probe for a new driver
|
||||
* @dev: the device to reprobe
|
||||
*
|
||||
* This function detaches the attached driver (if any) for the given
|
||||
* device and restarts the driver probing process. It is intended
|
||||
* to use if probing criteria changed during a devices lifetime and
|
||||
* driver attachment should change accordingly.
|
||||
*/
|
||||
int device_reprobe(struct device *dev)
|
||||
{
|
||||
if (dev->driver) {
|
||||
if (dev->parent) /* Needed for USB */
|
||||
down(&dev->parent->sem);
|
||||
device_release_driver(dev);
|
||||
if (dev->parent)
|
||||
up(&dev->parent->sem);
|
||||
}
|
||||
return bus_rescan_devices_helper(dev, NULL);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_reprobe);
|
||||
|
||||
struct bus_type *get_bus(struct bus_type *bus)
|
||||
{
|
||||
return bus ? container_of(subsys_get(&bus->subsys),
|
||||
struct bus_type, subsys) : NULL;
|
||||
}
|
||||
|
||||
void put_bus(struct bus_type * bus)
|
||||
{
|
||||
subsys_put(&bus->subsys);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* find_bus - locate bus by name.
|
||||
* @name: name of bus.
|
||||
*
|
||||
* Call kset_find_obj() to iterate over list of buses to
|
||||
* find a bus by name. Return bus if found.
|
||||
*
|
||||
* Note that kset_find_obj increments bus' reference count.
|
||||
*/
|
||||
#if 0
|
||||
struct bus_type * find_bus(char * name)
|
||||
{
|
||||
struct kobject * k = kset_find_obj(&bus_subsys.kset, name);
|
||||
return k ? to_bus(k) : NULL;
|
||||
}
|
||||
#endif /* 0 */
|
||||
|
||||
|
||||
/**
|
||||
* bus_add_attrs - Add default attributes for this bus.
|
||||
* @bus: Bus that has just been registered.
|
||||
*/
|
||||
|
||||
static int bus_add_attrs(struct bus_type * bus)
|
||||
{
|
||||
int error = 0;
|
||||
int i;
|
||||
|
||||
if (bus->bus_attrs) {
|
||||
for (i = 0; attr_name(bus->bus_attrs[i]); i++) {
|
||||
if ((error = bus_create_file(bus,&bus->bus_attrs[i])))
|
||||
goto Err;
|
||||
}
|
||||
}
|
||||
Done:
|
||||
return error;
|
||||
Err:
|
||||
while (--i >= 0)
|
||||
bus_remove_file(bus,&bus->bus_attrs[i]);
|
||||
goto Done;
|
||||
}
|
||||
|
||||
static void bus_remove_attrs(struct bus_type * bus)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (bus->bus_attrs) {
|
||||
for (i = 0; attr_name(bus->bus_attrs[i]); i++)
|
||||
bus_remove_file(bus,&bus->bus_attrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void klist_devices_get(struct klist_node *n)
|
||||
{
|
||||
struct device *dev = container_of(n, struct device, knode_bus);
|
||||
|
||||
get_device(dev);
|
||||
}
|
||||
|
||||
static void klist_devices_put(struct klist_node *n)
|
||||
{
|
||||
struct device *dev = container_of(n, struct device, knode_bus);
|
||||
|
||||
put_device(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* bus_register - register a bus with the system.
|
||||
* @bus: bus.
|
||||
*
|
||||
* Once we have that, we registered the bus with the kobject
|
||||
* infrastructure, then register the children subsystems it has:
|
||||
* the devices and drivers that belong to the bus.
|
||||
*/
|
||||
int bus_register(struct bus_type * bus)
|
||||
{
|
||||
int retval;
|
||||
|
||||
BLOCKING_INIT_NOTIFIER_HEAD(&bus->bus_notifier);
|
||||
|
||||
retval = kobject_set_name(&bus->subsys.kset.kobj, "%s", bus->name);
|
||||
if (retval)
|
||||
goto out;
|
||||
|
||||
subsys_set_kset(bus, bus_subsys);
|
||||
retval = subsystem_register(&bus->subsys);
|
||||
if (retval)
|
||||
goto out;
|
||||
|
||||
kobject_set_name(&bus->devices.kobj, "devices");
|
||||
bus->devices.subsys = &bus->subsys;
|
||||
retval = kset_register(&bus->devices);
|
||||
if (retval)
|
||||
goto bus_devices_fail;
|
||||
|
||||
kobject_set_name(&bus->drivers.kobj, "drivers");
|
||||
bus->drivers.subsys = &bus->subsys;
|
||||
bus->drivers.ktype = &ktype_driver;
|
||||
retval = kset_register(&bus->drivers);
|
||||
if (retval)
|
||||
goto bus_drivers_fail;
|
||||
|
||||
klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put);
|
||||
klist_init(&bus->klist_drivers, NULL, NULL);
|
||||
retval = bus_add_attrs(bus);
|
||||
if (retval)
|
||||
goto bus_attrs_fail;
|
||||
|
||||
pr_debug("bus type '%s' registered\n", bus->name);
|
||||
return 0;
|
||||
|
||||
bus_attrs_fail:
|
||||
kset_unregister(&bus->drivers);
|
||||
bus_drivers_fail:
|
||||
kset_unregister(&bus->devices);
|
||||
bus_devices_fail:
|
||||
subsystem_unregister(&bus->subsys);
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* bus_unregister - remove a bus from the system
|
||||
* @bus: bus.
|
||||
*
|
||||
* Unregister the child subsystems and the bus itself.
|
||||
* Finally, we call put_bus() to release the refcount
|
||||
*/
|
||||
void bus_unregister(struct bus_type * bus)
|
||||
{
|
||||
pr_debug("bus %s: unregistering\n", bus->name);
|
||||
bus_remove_attrs(bus);
|
||||
kset_unregister(&bus->drivers);
|
||||
kset_unregister(&bus->devices);
|
||||
subsystem_unregister(&bus->subsys);
|
||||
}
|
||||
|
||||
int bus_register_notifier(struct bus_type *bus, struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_register(&bus->bus_notifier, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bus_register_notifier);
|
||||
|
||||
int bus_unregister_notifier(struct bus_type *bus, struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_unregister(&bus->bus_notifier, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bus_unregister_notifier);
|
||||
|
||||
int __init buses_init(void)
|
||||
{
|
||||
return subsystem_register(&bus_subsys);
|
||||
}
|
||||
|
||||
|
||||
EXPORT_SYMBOL_GPL(bus_for_each_dev);
|
||||
EXPORT_SYMBOL_GPL(bus_find_device);
|
||||
EXPORT_SYMBOL_GPL(bus_for_each_drv);
|
||||
|
||||
EXPORT_SYMBOL_GPL(bus_register);
|
||||
EXPORT_SYMBOL_GPL(bus_unregister);
|
||||
EXPORT_SYMBOL_GPL(bus_rescan_devices);
|
||||
|
||||
EXPORT_SYMBOL_GPL(bus_create_file);
|
||||
EXPORT_SYMBOL_GPL(bus_remove_file);
|
||||
947
drivers/base/class.c
Normal file
947
drivers/base/class.c
Normal file
@@ -0,0 +1,947 @@
|
||||
/*
|
||||
* class.c - basic device class management
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* Copyright (c) 2002-3 Open Source Development Labs
|
||||
* Copyright (c) 2003-2004 Greg Kroah-Hartman
|
||||
* Copyright (c) 2003-2004 IBM Corp.
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include "base.h"
|
||||
|
||||
extern struct subsystem devices_subsys;
|
||||
|
||||
#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)
|
||||
#define to_class(obj) container_of(obj, struct class, subsys.kset.kobj)
|
||||
|
||||
static ssize_t
|
||||
class_attr_show(struct kobject * kobj, struct attribute * attr, char * buf)
|
||||
{
|
||||
struct class_attribute * class_attr = to_class_attr(attr);
|
||||
struct class * dc = to_class(kobj);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (class_attr->show)
|
||||
ret = class_attr->show(dc, buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
class_attr_store(struct kobject * kobj, struct attribute * attr,
|
||||
const char * buf, size_t count)
|
||||
{
|
||||
struct class_attribute * class_attr = to_class_attr(attr);
|
||||
struct class * dc = to_class(kobj);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (class_attr->store)
|
||||
ret = class_attr->store(dc, buf, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void class_release(struct kobject * kobj)
|
||||
{
|
||||
struct class *class = to_class(kobj);
|
||||
|
||||
pr_debug("class '%s': release.\n", class->name);
|
||||
|
||||
if (class->class_release)
|
||||
class->class_release(class);
|
||||
else
|
||||
pr_debug("class '%s' does not have a release() function, "
|
||||
"be careful\n", class->name);
|
||||
}
|
||||
|
||||
static struct sysfs_ops class_sysfs_ops = {
|
||||
.show = class_attr_show,
|
||||
.store = class_attr_store,
|
||||
};
|
||||
|
||||
static struct kobj_type ktype_class = {
|
||||
.sysfs_ops = &class_sysfs_ops,
|
||||
.release = class_release,
|
||||
};
|
||||
|
||||
/* Hotplug events for classes go to the class_obj subsys */
|
||||
static decl_subsys(class, &ktype_class, NULL);
|
||||
|
||||
|
||||
int class_create_file(struct class * cls, const struct class_attribute * attr)
|
||||
{
|
||||
int error;
|
||||
if (cls) {
|
||||
error = sysfs_create_file(&cls->subsys.kset.kobj, &attr->attr);
|
||||
} else
|
||||
error = -EINVAL;
|
||||
return error;
|
||||
}
|
||||
|
||||
void class_remove_file(struct class * cls, const struct class_attribute * attr)
|
||||
{
|
||||
if (cls)
|
||||
sysfs_remove_file(&cls->subsys.kset.kobj, &attr->attr);
|
||||
}
|
||||
|
||||
static struct class *class_get(struct class *cls)
|
||||
{
|
||||
if (cls)
|
||||
return container_of(subsys_get(&cls->subsys), struct class, subsys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void class_put(struct class * cls)
|
||||
{
|
||||
if (cls)
|
||||
subsys_put(&cls->subsys);
|
||||
}
|
||||
|
||||
|
||||
static int add_class_attrs(struct class * cls)
|
||||
{
|
||||
int i;
|
||||
int error = 0;
|
||||
|
||||
if (cls->class_attrs) {
|
||||
for (i = 0; attr_name(cls->class_attrs[i]); i++) {
|
||||
error = class_create_file(cls,&cls->class_attrs[i]);
|
||||
if (error)
|
||||
goto Err;
|
||||
}
|
||||
}
|
||||
Done:
|
||||
return error;
|
||||
Err:
|
||||
while (--i >= 0)
|
||||
class_remove_file(cls,&cls->class_attrs[i]);
|
||||
goto Done;
|
||||
}
|
||||
|
||||
static void remove_class_attrs(struct class * cls)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (cls->class_attrs) {
|
||||
for (i = 0; attr_name(cls->class_attrs[i]); i++)
|
||||
class_remove_file(cls,&cls->class_attrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int class_register(struct class * cls)
|
||||
{
|
||||
int error;
|
||||
|
||||
pr_debug("device class '%s': registering\n", cls->name);
|
||||
|
||||
INIT_LIST_HEAD(&cls->children);
|
||||
INIT_LIST_HEAD(&cls->devices);
|
||||
INIT_LIST_HEAD(&cls->interfaces);
|
||||
init_MUTEX(&cls->sem);
|
||||
error = kobject_set_name(&cls->subsys.kset.kobj, "%s", cls->name);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
subsys_set_kset(cls, class_subsys);
|
||||
|
||||
error = subsystem_register(&cls->subsys);
|
||||
if (!error) {
|
||||
error = add_class_attrs(class_get(cls));
|
||||
class_put(cls);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void class_unregister(struct class * cls)
|
||||
{
|
||||
pr_debug("device class '%s': unregistering\n", cls->name);
|
||||
kobject_unregister(cls->virtual_dir);
|
||||
remove_class_attrs(cls);
|
||||
subsystem_unregister(&cls->subsys);
|
||||
}
|
||||
|
||||
static void class_create_release(struct class *cls)
|
||||
{
|
||||
pr_debug("%s called for %s\n", __FUNCTION__, cls->name);
|
||||
kfree(cls);
|
||||
}
|
||||
|
||||
static void class_device_create_release(struct class_device *class_dev)
|
||||
{
|
||||
pr_debug("%s called for %s\n", __FUNCTION__, class_dev->class_id);
|
||||
kfree(class_dev);
|
||||
}
|
||||
|
||||
/* needed to allow these devices to have parent class devices */
|
||||
static int class_device_create_uevent(struct class_device *class_dev,
|
||||
char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
pr_debug("%s called for %s\n", __FUNCTION__, class_dev->class_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* class_create - create a struct class structure
|
||||
* @owner: pointer to the module that is to "own" this struct class
|
||||
* @name: pointer to a string for the name of this class.
|
||||
*
|
||||
* This is used to create a struct class pointer that can then be used
|
||||
* in calls to class_device_create().
|
||||
*
|
||||
* Note, the pointer created here is to be destroyed when finished by
|
||||
* making a call to class_destroy().
|
||||
*/
|
||||
struct class *class_create(struct module *owner, const char *name)
|
||||
{
|
||||
struct class *cls;
|
||||
int retval;
|
||||
|
||||
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
|
||||
if (!cls) {
|
||||
retval = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
cls->name = name;
|
||||
cls->owner = owner;
|
||||
cls->class_release = class_create_release;
|
||||
cls->release = class_device_create_release;
|
||||
|
||||
retval = class_register(cls);
|
||||
if (retval)
|
||||
goto error;
|
||||
|
||||
return cls;
|
||||
|
||||
error:
|
||||
kfree(cls);
|
||||
return ERR_PTR(retval);
|
||||
}
|
||||
|
||||
/**
|
||||
* class_destroy - destroys a struct class structure
|
||||
* @cls: pointer to the struct class that is to be destroyed
|
||||
*
|
||||
* Note, the pointer to be destroyed must have been created with a call
|
||||
* to class_create().
|
||||
*/
|
||||
void class_destroy(struct class *cls)
|
||||
{
|
||||
if ((cls == NULL) || (IS_ERR(cls)))
|
||||
return;
|
||||
|
||||
class_unregister(cls);
|
||||
}
|
||||
|
||||
/* Class Device Stuff */
|
||||
|
||||
int class_device_create_file(struct class_device * class_dev,
|
||||
const struct class_device_attribute * attr)
|
||||
{
|
||||
int error = -EINVAL;
|
||||
if (class_dev)
|
||||
error = sysfs_create_file(&class_dev->kobj, &attr->attr);
|
||||
return error;
|
||||
}
|
||||
|
||||
void class_device_remove_file(struct class_device * class_dev,
|
||||
const struct class_device_attribute * attr)
|
||||
{
|
||||
if (class_dev)
|
||||
sysfs_remove_file(&class_dev->kobj, &attr->attr);
|
||||
}
|
||||
|
||||
int class_device_create_bin_file(struct class_device *class_dev,
|
||||
struct bin_attribute *attr)
|
||||
{
|
||||
int error = -EINVAL;
|
||||
if (class_dev)
|
||||
error = sysfs_create_bin_file(&class_dev->kobj, attr);
|
||||
return error;
|
||||
}
|
||||
|
||||
void class_device_remove_bin_file(struct class_device *class_dev,
|
||||
struct bin_attribute *attr)
|
||||
{
|
||||
if (class_dev)
|
||||
sysfs_remove_bin_file(&class_dev->kobj, attr);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
class_device_attr_show(struct kobject * kobj, struct attribute * attr,
|
||||
char * buf)
|
||||
{
|
||||
struct class_device_attribute * class_dev_attr = to_class_dev_attr(attr);
|
||||
struct class_device * cd = to_class_dev(kobj);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (class_dev_attr->show)
|
||||
ret = class_dev_attr->show(cd, buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
class_device_attr_store(struct kobject * kobj, struct attribute * attr,
|
||||
const char * buf, size_t count)
|
||||
{
|
||||
struct class_device_attribute * class_dev_attr = to_class_dev_attr(attr);
|
||||
struct class_device * cd = to_class_dev(kobj);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (class_dev_attr->store)
|
||||
ret = class_dev_attr->store(cd, buf, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sysfs_ops class_dev_sysfs_ops = {
|
||||
.show = class_device_attr_show,
|
||||
.store = class_device_attr_store,
|
||||
};
|
||||
|
||||
static void class_dev_release(struct kobject * kobj)
|
||||
{
|
||||
struct class_device *cd = to_class_dev(kobj);
|
||||
struct class * cls = cd->class;
|
||||
|
||||
pr_debug("device class '%s': release.\n", cd->class_id);
|
||||
|
||||
kfree(cd->devt_attr);
|
||||
cd->devt_attr = NULL;
|
||||
|
||||
if (cd->release)
|
||||
cd->release(cd);
|
||||
else if (cls->release)
|
||||
cls->release(cd);
|
||||
else {
|
||||
printk(KERN_ERR "Class Device '%s' does not have a release() function, "
|
||||
"it is broken and must be fixed.\n",
|
||||
cd->class_id);
|
||||
WARN_ON(1);
|
||||
}
|
||||
}
|
||||
|
||||
static struct kobj_type ktype_class_device = {
|
||||
.sysfs_ops = &class_dev_sysfs_ops,
|
||||
.release = class_dev_release,
|
||||
};
|
||||
|
||||
static int class_uevent_filter(struct kset *kset, struct kobject *kobj)
|
||||
{
|
||||
struct kobj_type *ktype = get_ktype(kobj);
|
||||
|
||||
if (ktype == &ktype_class_device) {
|
||||
struct class_device *class_dev = to_class_dev(kobj);
|
||||
if (class_dev->class)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *class_uevent_name(struct kset *kset, struct kobject *kobj)
|
||||
{
|
||||
struct class_device *class_dev = to_class_dev(kobj);
|
||||
|
||||
return class_dev->class->name;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SYSFS_DEPRECATED
|
||||
char *make_class_name(const char *name, struct kobject *kobj)
|
||||
{
|
||||
char *class_name;
|
||||
int size;
|
||||
|
||||
size = strlen(name) + strlen(kobject_name(kobj)) + 2;
|
||||
|
||||
class_name = kmalloc(size, GFP_KERNEL);
|
||||
if (!class_name)
|
||||
return NULL;
|
||||
|
||||
strcpy(class_name, name);
|
||||
strcat(class_name, ":");
|
||||
strcat(class_name, kobject_name(kobj));
|
||||
return class_name;
|
||||
}
|
||||
|
||||
static int deprecated_class_uevent(char **envp, int num_envp, int *cur_index,
|
||||
char *buffer, int buffer_size,
|
||||
int *cur_len,
|
||||
struct class_device *class_dev)
|
||||
{
|
||||
struct device *dev = class_dev->dev;
|
||||
char *path;
|
||||
|
||||
if (!dev)
|
||||
return 0;
|
||||
|
||||
/* add device, backing this class device (deprecated) */
|
||||
path = kobject_get_path(&dev->kobj, GFP_KERNEL);
|
||||
|
||||
add_uevent_var(envp, num_envp, cur_index, buffer, buffer_size,
|
||||
cur_len, "PHYSDEVPATH=%s", path);
|
||||
kfree(path);
|
||||
|
||||
if (dev->bus)
|
||||
add_uevent_var(envp, num_envp, cur_index,
|
||||
buffer, buffer_size, cur_len,
|
||||
"PHYSDEVBUS=%s", dev->bus->name);
|
||||
|
||||
if (dev->driver)
|
||||
add_uevent_var(envp, num_envp, cur_index,
|
||||
buffer, buffer_size, cur_len,
|
||||
"PHYSDEVDRIVER=%s", dev->driver->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int make_deprecated_class_device_links(struct class_device *class_dev)
|
||||
{
|
||||
char *class_name;
|
||||
int error;
|
||||
|
||||
if (!class_dev->dev)
|
||||
return 0;
|
||||
|
||||
class_name = make_class_name(class_dev->class->name, &class_dev->kobj);
|
||||
if (class_name)
|
||||
error = sysfs_create_link(&class_dev->dev->kobj,
|
||||
&class_dev->kobj, class_name);
|
||||
else
|
||||
error = -ENOMEM;
|
||||
kfree(class_name);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void remove_deprecated_class_device_links(struct class_device *class_dev)
|
||||
{
|
||||
char *class_name;
|
||||
|
||||
if (!class_dev->dev)
|
||||
return;
|
||||
|
||||
class_name = make_class_name(class_dev->class->name, &class_dev->kobj);
|
||||
if (class_name)
|
||||
sysfs_remove_link(&class_dev->dev->kobj, class_name);
|
||||
kfree(class_name);
|
||||
}
|
||||
#else
|
||||
static inline int deprecated_class_uevent(char **envp, int num_envp,
|
||||
int *cur_index, char *buffer,
|
||||
int buffer_size, int *cur_len,
|
||||
struct class_device *class_dev)
|
||||
{ return 0; }
|
||||
static inline int make_deprecated_class_device_links(struct class_device *cd)
|
||||
{ return 0; }
|
||||
static void remove_deprecated_class_device_links(struct class_device *cd)
|
||||
{ }
|
||||
#endif
|
||||
|
||||
static int class_uevent(struct kset *kset, struct kobject *kobj, char **envp,
|
||||
int num_envp, char *buffer, int buffer_size)
|
||||
{
|
||||
struct class_device *class_dev = to_class_dev(kobj);
|
||||
int i = 0;
|
||||
int length = 0;
|
||||
int retval = 0;
|
||||
|
||||
pr_debug("%s - name = %s\n", __FUNCTION__, class_dev->class_id);
|
||||
|
||||
deprecated_class_uevent(envp, num_envp, &i, buffer, buffer_size,
|
||||
&length, class_dev);
|
||||
|
||||
if (MAJOR(class_dev->devt)) {
|
||||
add_uevent_var(envp, num_envp, &i,
|
||||
buffer, buffer_size, &length,
|
||||
"MAJOR=%u", MAJOR(class_dev->devt));
|
||||
|
||||
add_uevent_var(envp, num_envp, &i,
|
||||
buffer, buffer_size, &length,
|
||||
"MINOR=%u", MINOR(class_dev->devt));
|
||||
}
|
||||
|
||||
/* terminate, set to next free slot, shrink available space */
|
||||
envp[i] = NULL;
|
||||
envp = &envp[i];
|
||||
num_envp -= i;
|
||||
buffer = &buffer[length];
|
||||
buffer_size -= length;
|
||||
|
||||
if (class_dev->uevent) {
|
||||
/* have the class device specific function add its stuff */
|
||||
retval = class_dev->uevent(class_dev, envp, num_envp,
|
||||
buffer, buffer_size);
|
||||
if (retval)
|
||||
pr_debug("class_dev->uevent() returned %d\n", retval);
|
||||
} else if (class_dev->class->uevent) {
|
||||
/* have the class specific function add its stuff */
|
||||
retval = class_dev->class->uevent(class_dev, envp, num_envp,
|
||||
buffer, buffer_size);
|
||||
if (retval)
|
||||
pr_debug("class->uevent() returned %d\n", retval);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct kset_uevent_ops class_uevent_ops = {
|
||||
.filter = class_uevent_filter,
|
||||
.name = class_uevent_name,
|
||||
.uevent = class_uevent,
|
||||
};
|
||||
|
||||
static decl_subsys(class_obj, &ktype_class_device, &class_uevent_ops);
|
||||
|
||||
|
||||
static int class_device_add_attrs(struct class_device * cd)
|
||||
{
|
||||
int i;
|
||||
int error = 0;
|
||||
struct class * cls = cd->class;
|
||||
|
||||
if (cls->class_dev_attrs) {
|
||||
for (i = 0; attr_name(cls->class_dev_attrs[i]); i++) {
|
||||
error = class_device_create_file(cd,
|
||||
&cls->class_dev_attrs[i]);
|
||||
if (error)
|
||||
goto Err;
|
||||
}
|
||||
}
|
||||
Done:
|
||||
return error;
|
||||
Err:
|
||||
while (--i >= 0)
|
||||
class_device_remove_file(cd,&cls->class_dev_attrs[i]);
|
||||
goto Done;
|
||||
}
|
||||
|
||||
static void class_device_remove_attrs(struct class_device * cd)
|
||||
{
|
||||
int i;
|
||||
struct class * cls = cd->class;
|
||||
|
||||
if (cls->class_dev_attrs) {
|
||||
for (i = 0; attr_name(cls->class_dev_attrs[i]); i++)
|
||||
class_device_remove_file(cd,&cls->class_dev_attrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int class_device_add_groups(struct class_device * cd)
|
||||
{
|
||||
int i;
|
||||
int error = 0;
|
||||
|
||||
if (cd->groups) {
|
||||
for (i = 0; cd->groups[i]; i++) {
|
||||
error = sysfs_create_group(&cd->kobj, cd->groups[i]);
|
||||
if (error) {
|
||||
while (--i >= 0)
|
||||
sysfs_remove_group(&cd->kobj, cd->groups[i]);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
out:
|
||||
return error;
|
||||
}
|
||||
|
||||
static void class_device_remove_groups(struct class_device * cd)
|
||||
{
|
||||
int i;
|
||||
if (cd->groups) {
|
||||
for (i = 0; cd->groups[i]; i++) {
|
||||
sysfs_remove_group(&cd->kobj, cd->groups[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t show_dev(struct class_device *class_dev, char *buf)
|
||||
{
|
||||
return print_dev_t(buf, class_dev->devt);
|
||||
}
|
||||
|
||||
static ssize_t store_uevent(struct class_device *class_dev,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
kobject_uevent(&class_dev->kobj, KOBJ_ADD);
|
||||
return count;
|
||||
}
|
||||
|
||||
void class_device_initialize(struct class_device *class_dev)
|
||||
{
|
||||
kobj_set_kset_s(class_dev, class_obj_subsys);
|
||||
kobject_init(&class_dev->kobj);
|
||||
INIT_LIST_HEAD(&class_dev->node);
|
||||
}
|
||||
|
||||
int class_device_add(struct class_device *class_dev)
|
||||
{
|
||||
struct class *parent_class = NULL;
|
||||
struct class_device *parent_class_dev = NULL;
|
||||
struct class_interface *class_intf;
|
||||
int error = -EINVAL;
|
||||
|
||||
class_dev = class_device_get(class_dev);
|
||||
if (!class_dev)
|
||||
return -EINVAL;
|
||||
|
||||
if (!strlen(class_dev->class_id))
|
||||
goto out1;
|
||||
|
||||
parent_class = class_get(class_dev->class);
|
||||
if (!parent_class)
|
||||
goto out1;
|
||||
|
||||
parent_class_dev = class_device_get(class_dev->parent);
|
||||
|
||||
pr_debug("CLASS: registering class device: ID = '%s'\n",
|
||||
class_dev->class_id);
|
||||
|
||||
/* first, register with generic layer. */
|
||||
error = kobject_set_name(&class_dev->kobj, "%s", class_dev->class_id);
|
||||
if (error)
|
||||
goto out2;
|
||||
|
||||
if (parent_class_dev)
|
||||
class_dev->kobj.parent = &parent_class_dev->kobj;
|
||||
else
|
||||
class_dev->kobj.parent = &parent_class->subsys.kset.kobj;
|
||||
|
||||
error = kobject_add(&class_dev->kobj);
|
||||
if (error)
|
||||
goto out2;
|
||||
|
||||
/* add the needed attributes to this device */
|
||||
error = sysfs_create_link(&class_dev->kobj,
|
||||
&parent_class->subsys.kset.kobj, "subsystem");
|
||||
if (error)
|
||||
goto out3;
|
||||
class_dev->uevent_attr.attr.name = "uevent";
|
||||
class_dev->uevent_attr.attr.mode = S_IWUSR;
|
||||
class_dev->uevent_attr.attr.owner = parent_class->owner;
|
||||
class_dev->uevent_attr.store = store_uevent;
|
||||
error = class_device_create_file(class_dev, &class_dev->uevent_attr);
|
||||
if (error)
|
||||
goto out3;
|
||||
|
||||
if (MAJOR(class_dev->devt)) {
|
||||
struct class_device_attribute *attr;
|
||||
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
|
||||
if (!attr) {
|
||||
error = -ENOMEM;
|
||||
goto out4;
|
||||
}
|
||||
attr->attr.name = "dev";
|
||||
attr->attr.mode = S_IRUGO;
|
||||
attr->attr.owner = parent_class->owner;
|
||||
attr->show = show_dev;
|
||||
error = class_device_create_file(class_dev, attr);
|
||||
if (error) {
|
||||
kfree(attr);
|
||||
goto out4;
|
||||
}
|
||||
|
||||
class_dev->devt_attr = attr;
|
||||
}
|
||||
|
||||
error = class_device_add_attrs(class_dev);
|
||||
if (error)
|
||||
goto out5;
|
||||
|
||||
if (class_dev->dev) {
|
||||
error = sysfs_create_link(&class_dev->kobj,
|
||||
&class_dev->dev->kobj, "device");
|
||||
if (error)
|
||||
goto out6;
|
||||
}
|
||||
|
||||
error = class_device_add_groups(class_dev);
|
||||
if (error)
|
||||
goto out7;
|
||||
|
||||
error = make_deprecated_class_device_links(class_dev);
|
||||
if (error)
|
||||
goto out8;
|
||||
|
||||
kobject_uevent(&class_dev->kobj, KOBJ_ADD);
|
||||
|
||||
/* notify any interfaces this device is now here */
|
||||
down(&parent_class->sem);
|
||||
list_add_tail(&class_dev->node, &parent_class->children);
|
||||
list_for_each_entry(class_intf, &parent_class->interfaces, node) {
|
||||
if (class_intf->add)
|
||||
class_intf->add(class_dev, class_intf);
|
||||
}
|
||||
up(&parent_class->sem);
|
||||
|
||||
goto out1;
|
||||
|
||||
out8:
|
||||
class_device_remove_groups(class_dev);
|
||||
out7:
|
||||
if (class_dev->dev)
|
||||
sysfs_remove_link(&class_dev->kobj, "device");
|
||||
out6:
|
||||
class_device_remove_attrs(class_dev);
|
||||
out5:
|
||||
if (class_dev->devt_attr)
|
||||
class_device_remove_file(class_dev, class_dev->devt_attr);
|
||||
out4:
|
||||
class_device_remove_file(class_dev, &class_dev->uevent_attr);
|
||||
out3:
|
||||
kobject_del(&class_dev->kobj);
|
||||
out2:
|
||||
if(parent_class_dev)
|
||||
class_device_put(parent_class_dev);
|
||||
class_put(parent_class);
|
||||
out1:
|
||||
class_device_put(class_dev);
|
||||
return error;
|
||||
}
|
||||
|
||||
int class_device_register(struct class_device *class_dev)
|
||||
{
|
||||
class_device_initialize(class_dev);
|
||||
return class_device_add(class_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* class_device_create - creates a class device and registers it with sysfs
|
||||
* @cls: pointer to the struct class that this device should be registered to.
|
||||
* @parent: pointer to the parent struct class_device of this new device, if any.
|
||||
* @devt: the dev_t for the char device to be added.
|
||||
* @device: a pointer to a struct device that is assiociated with this class device.
|
||||
* @fmt: string for the class device's name
|
||||
*
|
||||
* This function can be used by char device classes. A struct
|
||||
* class_device will be created in sysfs, registered to the specified
|
||||
* class.
|
||||
* A "dev" file will be created, showing the dev_t for the device, if
|
||||
* the dev_t is not 0,0.
|
||||
* If a pointer to a parent struct class_device is passed in, the newly
|
||||
* created struct class_device will be a child of that device in sysfs.
|
||||
* The pointer to the struct class_device will be returned from the
|
||||
* call. Any further sysfs files that might be required can be created
|
||||
* using this pointer.
|
||||
*
|
||||
* Note: the struct class passed to this function must have previously
|
||||
* been created with a call to class_create().
|
||||
*/
|
||||
struct class_device *class_device_create(struct class *cls,
|
||||
struct class_device *parent,
|
||||
dev_t devt,
|
||||
struct device *device,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
struct class_device *class_dev = NULL;
|
||||
int retval = -ENODEV;
|
||||
|
||||
if (cls == NULL || IS_ERR(cls))
|
||||
goto error;
|
||||
|
||||
class_dev = kzalloc(sizeof(*class_dev), GFP_KERNEL);
|
||||
if (!class_dev) {
|
||||
retval = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
class_dev->devt = devt;
|
||||
class_dev->dev = device;
|
||||
class_dev->class = cls;
|
||||
class_dev->parent = parent;
|
||||
class_dev->release = class_device_create_release;
|
||||
class_dev->uevent = class_device_create_uevent;
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(class_dev->class_id, BUS_ID_SIZE, fmt, args);
|
||||
va_end(args);
|
||||
retval = class_device_register(class_dev);
|
||||
if (retval)
|
||||
goto error;
|
||||
|
||||
return class_dev;
|
||||
|
||||
error:
|
||||
kfree(class_dev);
|
||||
return ERR_PTR(retval);
|
||||
}
|
||||
|
||||
void class_device_del(struct class_device *class_dev)
|
||||
{
|
||||
struct class *parent_class = class_dev->class;
|
||||
struct class_device *parent_device = class_dev->parent;
|
||||
struct class_interface *class_intf;
|
||||
|
||||
if (parent_class) {
|
||||
down(&parent_class->sem);
|
||||
list_del_init(&class_dev->node);
|
||||
list_for_each_entry(class_intf, &parent_class->interfaces, node)
|
||||
if (class_intf->remove)
|
||||
class_intf->remove(class_dev, class_intf);
|
||||
up(&parent_class->sem);
|
||||
}
|
||||
|
||||
if (class_dev->dev) {
|
||||
remove_deprecated_class_device_links(class_dev);
|
||||
sysfs_remove_link(&class_dev->kobj, "device");
|
||||
}
|
||||
sysfs_remove_link(&class_dev->kobj, "subsystem");
|
||||
class_device_remove_file(class_dev, &class_dev->uevent_attr);
|
||||
if (class_dev->devt_attr)
|
||||
class_device_remove_file(class_dev, class_dev->devt_attr);
|
||||
class_device_remove_attrs(class_dev);
|
||||
class_device_remove_groups(class_dev);
|
||||
|
||||
kobject_uevent(&class_dev->kobj, KOBJ_REMOVE);
|
||||
kobject_del(&class_dev->kobj);
|
||||
|
||||
class_device_put(parent_device);
|
||||
class_put(parent_class);
|
||||
}
|
||||
|
||||
void class_device_unregister(struct class_device *class_dev)
|
||||
{
|
||||
pr_debug("CLASS: Unregistering class device. ID = '%s'\n",
|
||||
class_dev->class_id);
|
||||
class_device_del(class_dev);
|
||||
class_device_put(class_dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* class_device_destroy - removes a class device that was created with class_device_create()
|
||||
* @cls: the pointer to the struct class that this device was registered * with.
|
||||
* @devt: the dev_t of the device that was previously registered.
|
||||
*
|
||||
* This call unregisters and cleans up a class device that was created with a
|
||||
* call to class_device_create()
|
||||
*/
|
||||
void class_device_destroy(struct class *cls, dev_t devt)
|
||||
{
|
||||
struct class_device *class_dev = NULL;
|
||||
struct class_device *class_dev_tmp;
|
||||
|
||||
down(&cls->sem);
|
||||
list_for_each_entry(class_dev_tmp, &cls->children, node) {
|
||||
if (class_dev_tmp->devt == devt) {
|
||||
class_dev = class_dev_tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
up(&cls->sem);
|
||||
|
||||
if (class_dev)
|
||||
class_device_unregister(class_dev);
|
||||
}
|
||||
|
||||
struct class_device * class_device_get(struct class_device *class_dev)
|
||||
{
|
||||
if (class_dev)
|
||||
return to_class_dev(kobject_get(&class_dev->kobj));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void class_device_put(struct class_device *class_dev)
|
||||
{
|
||||
if (class_dev)
|
||||
kobject_put(&class_dev->kobj);
|
||||
}
|
||||
|
||||
|
||||
int class_interface_register(struct class_interface *class_intf)
|
||||
{
|
||||
struct class *parent;
|
||||
struct class_device *class_dev;
|
||||
struct device *dev;
|
||||
|
||||
if (!class_intf || !class_intf->class)
|
||||
return -ENODEV;
|
||||
|
||||
parent = class_get(class_intf->class);
|
||||
if (!parent)
|
||||
return -EINVAL;
|
||||
|
||||
down(&parent->sem);
|
||||
list_add_tail(&class_intf->node, &parent->interfaces);
|
||||
if (class_intf->add) {
|
||||
list_for_each_entry(class_dev, &parent->children, node)
|
||||
class_intf->add(class_dev, class_intf);
|
||||
}
|
||||
if (class_intf->add_dev) {
|
||||
list_for_each_entry(dev, &parent->devices, node)
|
||||
class_intf->add_dev(dev, class_intf);
|
||||
}
|
||||
up(&parent->sem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void class_interface_unregister(struct class_interface *class_intf)
|
||||
{
|
||||
struct class * parent = class_intf->class;
|
||||
struct class_device *class_dev;
|
||||
struct device *dev;
|
||||
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
down(&parent->sem);
|
||||
list_del_init(&class_intf->node);
|
||||
if (class_intf->remove) {
|
||||
list_for_each_entry(class_dev, &parent->children, node)
|
||||
class_intf->remove(class_dev, class_intf);
|
||||
}
|
||||
if (class_intf->remove_dev) {
|
||||
list_for_each_entry(dev, &parent->devices, node)
|
||||
class_intf->remove_dev(dev, class_intf);
|
||||
}
|
||||
up(&parent->sem);
|
||||
|
||||
class_put(parent);
|
||||
}
|
||||
|
||||
int __init classes_init(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = subsystem_register(&class_subsys);
|
||||
if (retval)
|
||||
return retval;
|
||||
|
||||
/* ick, this is ugly, the things we go through to keep from showing up
|
||||
* in sysfs... */
|
||||
subsystem_init(&class_obj_subsys);
|
||||
if (!class_obj_subsys.kset.subsys)
|
||||
class_obj_subsys.kset.subsys = &class_obj_subsys;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(class_create_file);
|
||||
EXPORT_SYMBOL_GPL(class_remove_file);
|
||||
EXPORT_SYMBOL_GPL(class_register);
|
||||
EXPORT_SYMBOL_GPL(class_unregister);
|
||||
EXPORT_SYMBOL_GPL(class_create);
|
||||
EXPORT_SYMBOL_GPL(class_destroy);
|
||||
|
||||
EXPORT_SYMBOL_GPL(class_device_register);
|
||||
EXPORT_SYMBOL_GPL(class_device_unregister);
|
||||
EXPORT_SYMBOL_GPL(class_device_initialize);
|
||||
EXPORT_SYMBOL_GPL(class_device_add);
|
||||
EXPORT_SYMBOL_GPL(class_device_del);
|
||||
EXPORT_SYMBOL_GPL(class_device_get);
|
||||
EXPORT_SYMBOL_GPL(class_device_put);
|
||||
EXPORT_SYMBOL_GPL(class_device_create);
|
||||
EXPORT_SYMBOL_GPL(class_device_destroy);
|
||||
EXPORT_SYMBOL_GPL(class_device_create_file);
|
||||
EXPORT_SYMBOL_GPL(class_device_remove_file);
|
||||
EXPORT_SYMBOL_GPL(class_device_create_bin_file);
|
||||
EXPORT_SYMBOL_GPL(class_device_remove_bin_file);
|
||||
|
||||
EXPORT_SYMBOL_GPL(class_interface_register);
|
||||
EXPORT_SYMBOL_GPL(class_interface_unregister);
|
||||
1186
drivers/base/core.c
Normal file
1186
drivers/base/core.c
Normal file
File diff suppressed because it is too large
Load Diff
156
drivers/base/cpu.c
Normal file
156
drivers/base/cpu.c
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* drivers/base/cpu.c - basic CPU class support
|
||||
*/
|
||||
|
||||
#include <linux/sysdev.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/topology.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/node.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
struct sysdev_class cpu_sysdev_class = {
|
||||
set_kset_name("cpu"),
|
||||
};
|
||||
EXPORT_SYMBOL(cpu_sysdev_class);
|
||||
|
||||
static struct sys_device *cpu_sys_devices[NR_CPUS];
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
static ssize_t show_online(struct sys_device *dev, char *buf)
|
||||
{
|
||||
struct cpu *cpu = container_of(dev, struct cpu, sysdev);
|
||||
|
||||
return sprintf(buf, "%u\n", !!cpu_online(cpu->sysdev.id));
|
||||
}
|
||||
|
||||
static ssize_t store_online(struct sys_device *dev, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct cpu *cpu = container_of(dev, struct cpu, sysdev);
|
||||
ssize_t ret;
|
||||
|
||||
switch (buf[0]) {
|
||||
case '0':
|
||||
ret = cpu_down(cpu->sysdev.id);
|
||||
if (!ret)
|
||||
kobject_uevent(&dev->kobj, KOBJ_OFFLINE);
|
||||
break;
|
||||
case '1':
|
||||
ret = cpu_up(cpu->sysdev.id);
|
||||
if (!ret)
|
||||
kobject_uevent(&dev->kobj, KOBJ_ONLINE);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
if (ret >= 0)
|
||||
ret = count;
|
||||
return ret;
|
||||
}
|
||||
static SYSDEV_ATTR(online, 0600, show_online, store_online);
|
||||
|
||||
static void __devinit register_cpu_control(struct cpu *cpu)
|
||||
{
|
||||
sysdev_create_file(&cpu->sysdev, &attr_online);
|
||||
}
|
||||
void unregister_cpu(struct cpu *cpu)
|
||||
{
|
||||
int logical_cpu = cpu->sysdev.id;
|
||||
|
||||
unregister_cpu_under_node(logical_cpu, cpu_to_node(logical_cpu));
|
||||
|
||||
sysdev_remove_file(&cpu->sysdev, &attr_online);
|
||||
|
||||
sysdev_unregister(&cpu->sysdev);
|
||||
cpu_sys_devices[logical_cpu] = NULL;
|
||||
return;
|
||||
}
|
||||
#else /* ... !CONFIG_HOTPLUG_CPU */
|
||||
static inline void register_cpu_control(struct cpu *cpu)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_HOTPLUG_CPU */
|
||||
|
||||
#ifdef CONFIG_KEXEC
|
||||
#include <linux/kexec.h>
|
||||
|
||||
static ssize_t show_crash_notes(struct sys_device *dev, char *buf)
|
||||
{
|
||||
struct cpu *cpu = container_of(dev, struct cpu, sysdev);
|
||||
ssize_t rc;
|
||||
unsigned long long addr;
|
||||
int cpunum;
|
||||
|
||||
cpunum = cpu->sysdev.id;
|
||||
|
||||
/*
|
||||
* Might be reading other cpu's data based on which cpu read thread
|
||||
* has been scheduled. But cpu data (memory) is allocated once during
|
||||
* boot up and this data does not change there after. Hence this
|
||||
* operation should be safe. No locking required.
|
||||
*/
|
||||
addr = __pa(per_cpu_ptr(crash_notes, cpunum));
|
||||
rc = sprintf(buf, "%Lx\n", addr);
|
||||
return rc;
|
||||
}
|
||||
static SYSDEV_ATTR(crash_notes, 0400, show_crash_notes, NULL);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* register_cpu - Setup a sysfs device for a CPU.
|
||||
* @cpu - cpu->hotpluggable field set to 1 will generate a control file in
|
||||
* sysfs for this CPU.
|
||||
* @num - CPU number to use when creating the device.
|
||||
*
|
||||
* Initialize and register the CPU device.
|
||||
*/
|
||||
int __devinit register_cpu(struct cpu *cpu, int num)
|
||||
{
|
||||
int error;
|
||||
cpu->node_id = cpu_to_node(num);
|
||||
cpu->sysdev.id = num;
|
||||
cpu->sysdev.cls = &cpu_sysdev_class;
|
||||
|
||||
error = sysdev_register(&cpu->sysdev);
|
||||
|
||||
if (!error && cpu->hotpluggable)
|
||||
register_cpu_control(cpu);
|
||||
if (!error)
|
||||
cpu_sys_devices[num] = &cpu->sysdev;
|
||||
if (!error)
|
||||
register_cpu_under_node(num, cpu_to_node(num));
|
||||
|
||||
#ifdef CONFIG_KEXEC
|
||||
if (!error)
|
||||
error = sysdev_create_file(&cpu->sysdev, &attr_crash_notes);
|
||||
#endif
|
||||
return error;
|
||||
}
|
||||
|
||||
struct sys_device *get_cpu_sysdev(unsigned cpu)
|
||||
{
|
||||
if (cpu < NR_CPUS)
|
||||
return cpu_sys_devices[cpu];
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(get_cpu_sysdev);
|
||||
|
||||
int __init cpu_dev_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = sysdev_class_register(&cpu_sysdev_class);
|
||||
#if defined(CONFIG_SCHED_MC) || defined(CONFIG_SCHED_SMT)
|
||||
if (!err)
|
||||
err = sched_create_sysfs_power_savings_entries(&cpu_sysdev_class);
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
413
drivers/base/dd.c
Normal file
413
drivers/base/dd.c
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* drivers/base/dd.c - The core device/driver interactions.
|
||||
*
|
||||
* This file contains the (sometimes tricky) code that controls the
|
||||
* interactions between devices and drivers, which primarily includes
|
||||
* driver binding and unbinding.
|
||||
*
|
||||
* All of this code used to exist in drivers/base/bus.c, but was
|
||||
* relocated to here in the name of compartmentalization (since it wasn't
|
||||
* strictly code just for the 'struct bus_type'.
|
||||
*
|
||||
* Copyright (c) 2002-5 Patrick Mochel
|
||||
* Copyright (c) 2002-3 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "base.h"
|
||||
#include "power/power.h"
|
||||
|
||||
#define to_drv(node) container_of(node, struct device_driver, kobj.entry)
|
||||
|
||||
|
||||
static void driver_bound(struct device *dev)
|
||||
{
|
||||
if (klist_node_attached(&dev->knode_driver)) {
|
||||
printk(KERN_WARNING "%s: device %s already bound\n",
|
||||
__FUNCTION__, kobject_name(&dev->kobj));
|
||||
return;
|
||||
}
|
||||
|
||||
pr_debug("bound device '%s' to driver '%s'\n",
|
||||
dev->bus_id, dev->driver->name);
|
||||
|
||||
if (dev->bus)
|
||||
blocking_notifier_call_chain(&dev->bus->bus_notifier,
|
||||
BUS_NOTIFY_BOUND_DRIVER, dev);
|
||||
|
||||
klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices);
|
||||
}
|
||||
|
||||
static int driver_sysfs_add(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sysfs_create_link(&dev->driver->kobj, &dev->kobj,
|
||||
kobject_name(&dev->kobj));
|
||||
if (ret == 0) {
|
||||
ret = sysfs_create_link(&dev->kobj, &dev->driver->kobj,
|
||||
"driver");
|
||||
if (ret)
|
||||
sysfs_remove_link(&dev->driver->kobj,
|
||||
kobject_name(&dev->kobj));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void driver_sysfs_remove(struct device *dev)
|
||||
{
|
||||
struct device_driver *drv = dev->driver;
|
||||
|
||||
if (drv) {
|
||||
sysfs_remove_link(&drv->kobj, kobject_name(&dev->kobj));
|
||||
sysfs_remove_link(&dev->kobj, "driver");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* device_bind_driver - bind a driver to one device.
|
||||
* @dev: device.
|
||||
*
|
||||
* Allow manual attachment of a driver to a device.
|
||||
* Caller must have already set @dev->driver.
|
||||
*
|
||||
* Note that this does not modify the bus reference count
|
||||
* nor take the bus's rwsem. Please verify those are accounted
|
||||
* for before calling this. (It is ok to call with no other effort
|
||||
* from a driver's probe() method.)
|
||||
*
|
||||
* This function must be called with @dev->sem held.
|
||||
*/
|
||||
int device_bind_driver(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = driver_sysfs_add(dev);
|
||||
if (!ret)
|
||||
driver_bound(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct stupid_thread_structure {
|
||||
struct device_driver *drv;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static atomic_t probe_count = ATOMIC_INIT(0);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue);
|
||||
|
||||
static int really_probe(void *void_data)
|
||||
{
|
||||
struct stupid_thread_structure *data = void_data;
|
||||
struct device_driver *drv = data->drv;
|
||||
struct device *dev = data->dev;
|
||||
int ret = 0;
|
||||
|
||||
atomic_inc(&probe_count);
|
||||
pr_debug("%s: Probing driver %s with device %s\n",
|
||||
drv->bus->name, drv->name, dev->bus_id);
|
||||
WARN_ON(!list_empty(&dev->devres_head));
|
||||
|
||||
dev->driver = drv;
|
||||
if (driver_sysfs_add(dev)) {
|
||||
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
|
||||
__FUNCTION__, dev->bus_id);
|
||||
goto probe_failed;
|
||||
}
|
||||
|
||||
if (dev->bus->probe) {
|
||||
ret = dev->bus->probe(dev);
|
||||
if (ret)
|
||||
goto probe_failed;
|
||||
} else if (drv->probe) {
|
||||
ret = drv->probe(dev);
|
||||
if (ret)
|
||||
goto probe_failed;
|
||||
}
|
||||
|
||||
driver_bound(dev);
|
||||
ret = 1;
|
||||
pr_debug("%s: Bound Device %s to Driver %s\n",
|
||||
drv->bus->name, dev->bus_id, drv->name);
|
||||
goto done;
|
||||
|
||||
probe_failed:
|
||||
devres_release_all(dev);
|
||||
driver_sysfs_remove(dev);
|
||||
dev->driver = NULL;
|
||||
|
||||
if (ret != -ENODEV && ret != -ENXIO) {
|
||||
/* driver matched but the probe failed */
|
||||
printk(KERN_WARNING
|
||||
"%s: probe of %s failed with error %d\n",
|
||||
drv->name, dev->bus_id, ret);
|
||||
}
|
||||
/*
|
||||
* Ignore errors returned by ->probe so that the next driver can try
|
||||
* its luck.
|
||||
*/
|
||||
ret = 0;
|
||||
done:
|
||||
kfree(data);
|
||||
atomic_dec(&probe_count);
|
||||
wake_up(&probe_waitqueue);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* driver_probe_done
|
||||
* Determine if the probe sequence is finished or not.
|
||||
*
|
||||
* Should somehow figure out how to use a semaphore, not an atomic variable...
|
||||
*/
|
||||
int driver_probe_done(void)
|
||||
{
|
||||
pr_debug("%s: probe_count = %d\n", __FUNCTION__,
|
||||
atomic_read(&probe_count));
|
||||
if (atomic_read(&probe_count))
|
||||
return -EBUSY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* driver_probe_device - attempt to bind device & driver together
|
||||
* @drv: driver to bind a device to
|
||||
* @dev: device to try to bind to the driver
|
||||
*
|
||||
* First, we call the bus's match function, if one present, which should
|
||||
* compare the device IDs the driver supports with the device IDs of the
|
||||
* device. Note we don't do this ourselves because we don't know the
|
||||
* format of the ID structures, nor what is to be considered a match and
|
||||
* what is not.
|
||||
*
|
||||
* This function returns 1 if a match is found, an error if one occurs
|
||||
* (that is not -ENODEV or -ENXIO), and 0 otherwise.
|
||||
*
|
||||
* This function must be called with @dev->sem held. When called for a
|
||||
* USB interface, @dev->parent->sem must be held as well.
|
||||
*/
|
||||
int driver_probe_device(struct device_driver * drv, struct device * dev)
|
||||
{
|
||||
struct stupid_thread_structure *data;
|
||||
struct task_struct *probe_task;
|
||||
int ret = 0;
|
||||
|
||||
if (!device_is_registered(dev))
|
||||
return -ENODEV;
|
||||
if (drv->bus->match && !drv->bus->match(dev, drv))
|
||||
goto done;
|
||||
|
||||
pr_debug("%s: Matched Device %s with Driver %s\n",
|
||||
drv->bus->name, dev->bus_id, drv->name);
|
||||
|
||||
data = kmalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
data->drv = drv;
|
||||
data->dev = dev;
|
||||
|
||||
if (drv->multithread_probe) {
|
||||
probe_task = kthread_run(really_probe, data,
|
||||
"probe-%s", dev->bus_id);
|
||||
if (IS_ERR(probe_task))
|
||||
ret = really_probe(data);
|
||||
} else
|
||||
ret = really_probe(data);
|
||||
|
||||
done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __device_attach(struct device_driver * drv, void * data)
|
||||
{
|
||||
struct device * dev = data;
|
||||
return driver_probe_device(drv, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* device_attach - try to attach device to a driver.
|
||||
* @dev: device.
|
||||
*
|
||||
* Walk the list of drivers that the bus has and call
|
||||
* driver_probe_device() for each pair. If a compatible
|
||||
* pair is found, break out and return.
|
||||
*
|
||||
* Returns 1 if the device was bound to a driver;
|
||||
* 0 if no matching device was found; error code otherwise.
|
||||
*
|
||||
* When called for a USB interface, @dev->parent->sem must be held.
|
||||
*/
|
||||
int device_attach(struct device * dev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
down(&dev->sem);
|
||||
if (dev->driver) {
|
||||
ret = device_bind_driver(dev);
|
||||
if (ret == 0)
|
||||
ret = 1;
|
||||
} else
|
||||
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
|
||||
up(&dev->sem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __driver_attach(struct device * dev, void * data)
|
||||
{
|
||||
struct device_driver * drv = data;
|
||||
|
||||
/*
|
||||
* Lock device and try to bind to it. We drop the error
|
||||
* here and always return 0, because we need to keep trying
|
||||
* to bind to devices and some drivers will return an error
|
||||
* simply if it didn't support the device.
|
||||
*
|
||||
* driver_probe_device() will spit a warning if there
|
||||
* is an error.
|
||||
*/
|
||||
|
||||
if (dev->parent) /* Needed for USB */
|
||||
down(&dev->parent->sem);
|
||||
down(&dev->sem);
|
||||
if (!dev->driver)
|
||||
driver_probe_device(drv, dev);
|
||||
up(&dev->sem);
|
||||
if (dev->parent)
|
||||
up(&dev->parent->sem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* driver_attach - try to bind driver to devices.
|
||||
* @drv: driver.
|
||||
*
|
||||
* Walk the list of devices that the bus has on it and try to
|
||||
* match the driver with each one. If driver_probe_device()
|
||||
* returns 0 and the @dev->driver is set, we've found a
|
||||
* compatible pair.
|
||||
*/
|
||||
int driver_attach(struct device_driver * drv)
|
||||
{
|
||||
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
|
||||
}
|
||||
|
||||
/**
|
||||
* device_release_driver - manually detach device from driver.
|
||||
* @dev: device.
|
||||
*
|
||||
* Manually detach device from driver.
|
||||
*
|
||||
* __device_release_driver() must be called with @dev->sem held.
|
||||
* When called for a USB interface, @dev->parent->sem must be held
|
||||
* as well.
|
||||
*/
|
||||
|
||||
static void __device_release_driver(struct device * dev)
|
||||
{
|
||||
struct device_driver * drv;
|
||||
|
||||
drv = dev->driver;
|
||||
if (drv) {
|
||||
get_driver(drv);
|
||||
driver_sysfs_remove(dev);
|
||||
sysfs_remove_link(&dev->kobj, "driver");
|
||||
klist_remove(&dev->knode_driver);
|
||||
|
||||
if (dev->bus)
|
||||
blocking_notifier_call_chain(&dev->bus->bus_notifier,
|
||||
BUS_NOTIFY_UNBIND_DRIVER,
|
||||
dev);
|
||||
|
||||
if (dev->bus && dev->bus->remove)
|
||||
dev->bus->remove(dev);
|
||||
else if (drv->remove)
|
||||
drv->remove(dev);
|
||||
devres_release_all(dev);
|
||||
dev->driver = NULL;
|
||||
put_driver(drv);
|
||||
}
|
||||
}
|
||||
|
||||
void device_release_driver(struct device * dev)
|
||||
{
|
||||
/*
|
||||
* If anyone calls device_release_driver() recursively from
|
||||
* within their ->remove callback for the same device, they
|
||||
* will deadlock right here.
|
||||
*/
|
||||
down(&dev->sem);
|
||||
__device_release_driver(dev);
|
||||
up(&dev->sem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* driver_detach - detach driver from all devices it controls.
|
||||
* @drv: driver.
|
||||
*/
|
||||
void driver_detach(struct device_driver * drv)
|
||||
{
|
||||
struct device * dev;
|
||||
|
||||
for (;;) {
|
||||
spin_lock(&drv->klist_devices.k_lock);
|
||||
if (list_empty(&drv->klist_devices.k_list)) {
|
||||
spin_unlock(&drv->klist_devices.k_lock);
|
||||
break;
|
||||
}
|
||||
dev = list_entry(drv->klist_devices.k_list.prev,
|
||||
struct device, knode_driver.n_node);
|
||||
get_device(dev);
|
||||
spin_unlock(&drv->klist_devices.k_lock);
|
||||
|
||||
if (dev->parent) /* Needed for USB */
|
||||
down(&dev->parent->sem);
|
||||
down(&dev->sem);
|
||||
if (dev->driver == drv)
|
||||
__device_release_driver(dev);
|
||||
up(&dev->sem);
|
||||
if (dev->parent)
|
||||
up(&dev->parent->sem);
|
||||
put_device(dev);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PCI_MULTITHREAD_PROBE
|
||||
static int __init wait_for_probes(void)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
|
||||
printk(KERN_INFO "%s: waiting for %d threads\n", __FUNCTION__,
|
||||
atomic_read(&probe_count));
|
||||
if (!atomic_read(&probe_count))
|
||||
return 0;
|
||||
while (atomic_read(&probe_count)) {
|
||||
prepare_to_wait(&probe_waitqueue, &wait, TASK_UNINTERRUPTIBLE);
|
||||
if (atomic_read(&probe_count))
|
||||
schedule();
|
||||
}
|
||||
finish_wait(&probe_waitqueue, &wait);
|
||||
return 0;
|
||||
}
|
||||
|
||||
core_initcall_sync(wait_for_probes);
|
||||
postcore_initcall_sync(wait_for_probes);
|
||||
arch_initcall_sync(wait_for_probes);
|
||||
subsys_initcall_sync(wait_for_probes);
|
||||
fs_initcall_sync(wait_for_probes);
|
||||
device_initcall_sync(wait_for_probes);
|
||||
late_initcall_sync(wait_for_probes);
|
||||
#endif
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_bind_driver);
|
||||
EXPORT_SYMBOL_GPL(device_release_driver);
|
||||
EXPORT_SYMBOL_GPL(device_attach);
|
||||
EXPORT_SYMBOL_GPL(driver_attach);
|
||||
|
||||
644
drivers/base/devres.c
Normal file
644
drivers/base/devres.c
Normal file
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
* drivers/base/devres.c - device resource management
|
||||
*
|
||||
* Copyright (c) 2006 SUSE Linux Products GmbH
|
||||
* Copyright (c) 2006 Tejun Heo <teheo@suse.de>
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
struct devres_node {
|
||||
struct list_head entry;
|
||||
dr_release_t release;
|
||||
#ifdef CONFIG_DEBUG_DEVRES
|
||||
const char *name;
|
||||
size_t size;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct devres {
|
||||
struct devres_node node;
|
||||
/* -- 3 pointers */
|
||||
unsigned long long data[]; /* guarantee ull alignment */
|
||||
};
|
||||
|
||||
struct devres_group {
|
||||
struct devres_node node[2];
|
||||
void *id;
|
||||
int color;
|
||||
/* -- 8 pointers */
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DEBUG_DEVRES
|
||||
static int log_devres = 0;
|
||||
module_param_named(log, log_devres, int, S_IRUGO | S_IWUSR);
|
||||
|
||||
static void set_node_dbginfo(struct devres_node *node, const char *name,
|
||||
size_t size)
|
||||
{
|
||||
node->name = name;
|
||||
node->size = size;
|
||||
}
|
||||
|
||||
static void devres_log(struct device *dev, struct devres_node *node,
|
||||
const char *op)
|
||||
{
|
||||
if (unlikely(log_devres))
|
||||
dev_printk(KERN_ERR, dev, "DEVRES %3s %p %s (%lu bytes)\n",
|
||||
op, node, node->name, (unsigned long)node->size);
|
||||
}
|
||||
#else /* CONFIG_DEBUG_DEVRES */
|
||||
#define set_node_dbginfo(node, n, s) do {} while (0)
|
||||
#define devres_log(dev, node, op) do {} while (0)
|
||||
#endif /* CONFIG_DEBUG_DEVRES */
|
||||
|
||||
/*
|
||||
* Release functions for devres group. These callbacks are used only
|
||||
* for identification.
|
||||
*/
|
||||
static void group_open_release(struct device *dev, void *res)
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
|
||||
static void group_close_release(struct device *dev, void *res)
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
|
||||
static struct devres_group * node_to_group(struct devres_node *node)
|
||||
{
|
||||
if (node->release == &group_open_release)
|
||||
return container_of(node, struct devres_group, node[0]);
|
||||
if (node->release == &group_close_release)
|
||||
return container_of(node, struct devres_group, node[1]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static __always_inline struct devres * alloc_dr(dr_release_t release,
|
||||
size_t size, gfp_t gfp)
|
||||
{
|
||||
size_t tot_size = sizeof(struct devres) + size;
|
||||
struct devres *dr;
|
||||
|
||||
dr = kmalloc_track_caller(tot_size, gfp);
|
||||
if (unlikely(!dr))
|
||||
return NULL;
|
||||
|
||||
memset(dr, 0, tot_size);
|
||||
INIT_LIST_HEAD(&dr->node.entry);
|
||||
dr->node.release = release;
|
||||
return dr;
|
||||
}
|
||||
|
||||
static void add_dr(struct device *dev, struct devres_node *node)
|
||||
{
|
||||
devres_log(dev, node, "ADD");
|
||||
BUG_ON(!list_empty(&node->entry));
|
||||
list_add_tail(&node->entry, &dev->devres_head);
|
||||
}
|
||||
|
||||
/**
|
||||
* devres_alloc - Allocate device resource data
|
||||
* @release: Release function devres will be associated with
|
||||
* @size: Allocation size
|
||||
* @gfp: Allocation flags
|
||||
*
|
||||
* allocate devres of @size bytes. The allocated area is zeroed, then
|
||||
* associated with @release. The returned pointer can be passed to
|
||||
* other devres_*() functions.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to allocated devres on success, NULL on failure.
|
||||
*/
|
||||
#ifdef CONFIG_DEBUG_DEVRES
|
||||
void * __devres_alloc(dr_release_t release, size_t size, gfp_t gfp,
|
||||
const char *name)
|
||||
{
|
||||
struct devres *dr;
|
||||
|
||||
dr = alloc_dr(release, size, gfp);
|
||||
if (unlikely(!dr))
|
||||
return NULL;
|
||||
set_node_dbginfo(&dr->node, name, size);
|
||||
return dr->data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__devres_alloc);
|
||||
#else
|
||||
void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
|
||||
{
|
||||
struct devres *dr;
|
||||
|
||||
dr = alloc_dr(release, size, gfp);
|
||||
if (unlikely(!dr))
|
||||
return NULL;
|
||||
return dr->data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_alloc);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* devres_free - Free device resource data
|
||||
* @res: Pointer to devres data to free
|
||||
*
|
||||
* Free devres created with devres_alloc().
|
||||
*/
|
||||
void devres_free(void *res)
|
||||
{
|
||||
if (res) {
|
||||
struct devres *dr = container_of(res, struct devres, data);
|
||||
|
||||
BUG_ON(!list_empty(&dr->node.entry));
|
||||
kfree(dr);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_free);
|
||||
|
||||
/**
|
||||
* devres_add - Register device resource
|
||||
* @dev: Device to add resource to
|
||||
* @res: Resource to register
|
||||
*
|
||||
* Register devres @res to @dev. @res should have been allocated
|
||||
* using devres_alloc(). On driver detach, the associated release
|
||||
* function will be invoked and devres will be freed automatically.
|
||||
*/
|
||||
void devres_add(struct device *dev, void *res)
|
||||
{
|
||||
struct devres *dr = container_of(res, struct devres, data);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
add_dr(dev, &dr->node);
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_add);
|
||||
|
||||
static struct devres *find_dr(struct device *dev, dr_release_t release,
|
||||
dr_match_t match, void *match_data)
|
||||
{
|
||||
struct devres_node *node;
|
||||
|
||||
list_for_each_entry_reverse(node, &dev->devres_head, entry) {
|
||||
struct devres *dr = container_of(node, struct devres, node);
|
||||
|
||||
if (node->release != release)
|
||||
continue;
|
||||
if (match && !match(dev, dr->data, match_data))
|
||||
continue;
|
||||
return dr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* devres_find - Find device resource
|
||||
* @dev: Device to lookup resource from
|
||||
* @release: Look for resources associated with this release function
|
||||
* @match: Match function (optional)
|
||||
* @match_data: Data for the match function
|
||||
*
|
||||
* Find the latest devres of @dev which is associated with @release
|
||||
* and for which @match returns 1. If @match is NULL, it's considered
|
||||
* to match all.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to found devres, NULL if not found.
|
||||
*/
|
||||
void * devres_find(struct device *dev, dr_release_t release,
|
||||
dr_match_t match, void *match_data)
|
||||
{
|
||||
struct devres *dr;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
dr = find_dr(dev, release, match, match_data);
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
|
||||
if (dr)
|
||||
return dr->data;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_find);
|
||||
|
||||
/**
|
||||
* devres_get - Find devres, if non-existent, add one atomically
|
||||
* @dev: Device to lookup or add devres for
|
||||
* @new_res: Pointer to new initialized devres to add if not found
|
||||
* @match: Match function (optional)
|
||||
* @match_data: Data for the match function
|
||||
*
|
||||
* Find the latest devres of @dev which has the same release function
|
||||
* as @new_res and for which @match return 1. If found, @new_res is
|
||||
* freed; otherwise, @new_res is added atomically.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to found or added devres.
|
||||
*/
|
||||
void * devres_get(struct device *dev, void *new_res,
|
||||
dr_match_t match, void *match_data)
|
||||
{
|
||||
struct devres *new_dr = container_of(new_res, struct devres, data);
|
||||
struct devres *dr;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
dr = find_dr(dev, new_dr->node.release, match, match_data);
|
||||
if (!dr) {
|
||||
add_dr(dev, &new_dr->node);
|
||||
dr = new_dr;
|
||||
new_dr = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
devres_free(new_dr);
|
||||
|
||||
return dr->data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_get);
|
||||
|
||||
/**
|
||||
* devres_remove - Find a device resource and remove it
|
||||
* @dev: Device to find resource from
|
||||
* @release: Look for resources associated with this release function
|
||||
* @match: Match function (optional)
|
||||
* @match_data: Data for the match function
|
||||
*
|
||||
* Find the latest devres of @dev associated with @release and for
|
||||
* which @match returns 1. If @match is NULL, it's considered to
|
||||
* match all. If found, the resource is removed atomically and
|
||||
* returned.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to removed devres on success, NULL if not found.
|
||||
*/
|
||||
void * devres_remove(struct device *dev, dr_release_t release,
|
||||
dr_match_t match, void *match_data)
|
||||
{
|
||||
struct devres *dr;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
dr = find_dr(dev, release, match, match_data);
|
||||
if (dr) {
|
||||
list_del_init(&dr->node.entry);
|
||||
devres_log(dev, &dr->node, "REM");
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
|
||||
if (dr)
|
||||
return dr->data;
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_remove);
|
||||
|
||||
/**
|
||||
* devres_destroy - Find a device resource and destroy it
|
||||
* @dev: Device to find resource from
|
||||
* @release: Look for resources associated with this release function
|
||||
* @match: Match function (optional)
|
||||
* @match_data: Data for the match function
|
||||
*
|
||||
* Find the latest devres of @dev associated with @release and for
|
||||
* which @match returns 1. If @match is NULL, it's considered to
|
||||
* match all. If found, the resource is removed atomically and freed.
|
||||
*
|
||||
* RETURNS:
|
||||
* 0 if devres is found and freed, -ENOENT if not found.
|
||||
*/
|
||||
int devres_destroy(struct device *dev, dr_release_t release,
|
||||
dr_match_t match, void *match_data)
|
||||
{
|
||||
void *res;
|
||||
|
||||
res = devres_remove(dev, release, match, match_data);
|
||||
if (unlikely(!res))
|
||||
return -ENOENT;
|
||||
|
||||
devres_free(res);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_destroy);
|
||||
|
||||
static int remove_nodes(struct device *dev,
|
||||
struct list_head *first, struct list_head *end,
|
||||
struct list_head *todo)
|
||||
{
|
||||
int cnt = 0, nr_groups = 0;
|
||||
struct list_head *cur;
|
||||
|
||||
/* First pass - move normal devres entries to @todo and clear
|
||||
* devres_group colors.
|
||||
*/
|
||||
cur = first;
|
||||
while (cur != end) {
|
||||
struct devres_node *node;
|
||||
struct devres_group *grp;
|
||||
|
||||
node = list_entry(cur, struct devres_node, entry);
|
||||
cur = cur->next;
|
||||
|
||||
grp = node_to_group(node);
|
||||
if (grp) {
|
||||
/* clear color of group markers in the first pass */
|
||||
grp->color = 0;
|
||||
nr_groups++;
|
||||
} else {
|
||||
/* regular devres entry */
|
||||
if (&node->entry == first)
|
||||
first = first->next;
|
||||
list_move_tail(&node->entry, todo);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nr_groups)
|
||||
return cnt;
|
||||
|
||||
/* Second pass - Scan groups and color them. A group gets
|
||||
* color value of two iff the group is wholly contained in
|
||||
* [cur, end). That is, for a closed group, both opening and
|
||||
* closing markers should be in the range, while just the
|
||||
* opening marker is enough for an open group.
|
||||
*/
|
||||
cur = first;
|
||||
while (cur != end) {
|
||||
struct devres_node *node;
|
||||
struct devres_group *grp;
|
||||
|
||||
node = list_entry(cur, struct devres_node, entry);
|
||||
cur = cur->next;
|
||||
|
||||
grp = node_to_group(node);
|
||||
BUG_ON(!grp || list_empty(&grp->node[0].entry));
|
||||
|
||||
grp->color++;
|
||||
if (list_empty(&grp->node[1].entry))
|
||||
grp->color++;
|
||||
|
||||
BUG_ON(grp->color <= 0 || grp->color > 2);
|
||||
if (grp->color == 2) {
|
||||
/* No need to update cur or end. The removed
|
||||
* nodes are always before both.
|
||||
*/
|
||||
list_move_tail(&grp->node[0].entry, todo);
|
||||
list_del_init(&grp->node[1].entry);
|
||||
}
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static int release_nodes(struct device *dev, struct list_head *first,
|
||||
struct list_head *end, unsigned long flags)
|
||||
{
|
||||
LIST_HEAD(todo);
|
||||
int cnt;
|
||||
struct devres *dr, *tmp;
|
||||
|
||||
cnt = remove_nodes(dev, first, end, &todo);
|
||||
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
|
||||
/* Release. Note that both devres and devres_group are
|
||||
* handled as devres in the following loop. This is safe.
|
||||
*/
|
||||
list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
|
||||
devres_log(dev, &dr->node, "REL");
|
||||
dr->node.release(dev, dr->data);
|
||||
kfree(dr);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* devres_release_all - Release all resources
|
||||
* @dev: Device to release resources for
|
||||
*
|
||||
* Release all resources associated with @dev. This function is
|
||||
* called on driver detach.
|
||||
*/
|
||||
int devres_release_all(struct device *dev)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
|
||||
flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* devres_open_group - Open a new devres group
|
||||
* @dev: Device to open devres group for
|
||||
* @id: Separator ID
|
||||
* @gfp: Allocation flags
|
||||
*
|
||||
* Open a new devres group for @dev with @id. For @id, using a
|
||||
* pointer to an object which won't be used for another group is
|
||||
* recommended. If @id is NULL, address-wise unique ID is created.
|
||||
*
|
||||
* RETURNS:
|
||||
* ID of the new group, NULL on failure.
|
||||
*/
|
||||
void * devres_open_group(struct device *dev, void *id, gfp_t gfp)
|
||||
{
|
||||
struct devres_group *grp;
|
||||
unsigned long flags;
|
||||
|
||||
grp = kmalloc(sizeof(*grp), gfp);
|
||||
if (unlikely(!grp))
|
||||
return NULL;
|
||||
|
||||
grp->node[0].release = &group_open_release;
|
||||
grp->node[1].release = &group_close_release;
|
||||
INIT_LIST_HEAD(&grp->node[0].entry);
|
||||
INIT_LIST_HEAD(&grp->node[1].entry);
|
||||
set_node_dbginfo(&grp->node[0], "grp<", 0);
|
||||
set_node_dbginfo(&grp->node[1], "grp>", 0);
|
||||
grp->id = grp;
|
||||
if (id)
|
||||
grp->id = id;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
add_dr(dev, &grp->node[0]);
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
return grp->id;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_open_group);
|
||||
|
||||
/* Find devres group with ID @id. If @id is NULL, look for the latest. */
|
||||
static struct devres_group * find_group(struct device *dev, void *id)
|
||||
{
|
||||
struct devres_node *node;
|
||||
|
||||
list_for_each_entry_reverse(node, &dev->devres_head, entry) {
|
||||
struct devres_group *grp;
|
||||
|
||||
if (node->release != &group_open_release)
|
||||
continue;
|
||||
|
||||
grp = container_of(node, struct devres_group, node[0]);
|
||||
|
||||
if (id) {
|
||||
if (grp->id == id)
|
||||
return grp;
|
||||
} else if (list_empty(&grp->node[1].entry))
|
||||
return grp;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* devres_close_group - Close a devres group
|
||||
* @dev: Device to close devres group for
|
||||
* @id: ID of target group, can be NULL
|
||||
*
|
||||
* Close the group identified by @id. If @id is NULL, the latest open
|
||||
* group is selected.
|
||||
*/
|
||||
void devres_close_group(struct device *dev, void *id)
|
||||
{
|
||||
struct devres_group *grp;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
|
||||
grp = find_group(dev, id);
|
||||
if (grp)
|
||||
add_dr(dev, &grp->node[1]);
|
||||
else
|
||||
WARN_ON(1);
|
||||
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_close_group);
|
||||
|
||||
/**
|
||||
* devres_remove_group - Remove a devres group
|
||||
* @dev: Device to remove group for
|
||||
* @id: ID of target group, can be NULL
|
||||
*
|
||||
* Remove the group identified by @id. If @id is NULL, the latest
|
||||
* open group is selected. Note that removing a group doesn't affect
|
||||
* any other resources.
|
||||
*/
|
||||
void devres_remove_group(struct device *dev, void *id)
|
||||
{
|
||||
struct devres_group *grp;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
|
||||
grp = find_group(dev, id);
|
||||
if (grp) {
|
||||
list_del_init(&grp->node[0].entry);
|
||||
list_del_init(&grp->node[1].entry);
|
||||
devres_log(dev, &grp->node[0], "REM");
|
||||
} else
|
||||
WARN_ON(1);
|
||||
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
|
||||
kfree(grp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_remove_group);
|
||||
|
||||
/**
|
||||
* devres_release_group - Release resources in a devres group
|
||||
* @dev: Device to release group for
|
||||
* @id: ID of target group, can be NULL
|
||||
*
|
||||
* Release all resources in the group identified by @id. If @id is
|
||||
* NULL, the latest open group is selected. The selected group and
|
||||
* groups properly nested inside the selected group are removed.
|
||||
*
|
||||
* RETURNS:
|
||||
* The number of released non-group resources.
|
||||
*/
|
||||
int devres_release_group(struct device *dev, void *id)
|
||||
{
|
||||
struct devres_group *grp;
|
||||
unsigned long flags;
|
||||
int cnt = 0;
|
||||
|
||||
spin_lock_irqsave(&dev->devres_lock, flags);
|
||||
|
||||
grp = find_group(dev, id);
|
||||
if (grp) {
|
||||
struct list_head *first = &grp->node[0].entry;
|
||||
struct list_head *end = &dev->devres_head;
|
||||
|
||||
if (!list_empty(&grp->node[1].entry))
|
||||
end = grp->node[1].entry.next;
|
||||
|
||||
cnt = release_nodes(dev, first, end, flags);
|
||||
} else {
|
||||
WARN_ON(1);
|
||||
spin_unlock_irqrestore(&dev->devres_lock, flags);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devres_release_group);
|
||||
|
||||
/*
|
||||
* Managed kzalloc/kfree
|
||||
*/
|
||||
static void devm_kzalloc_release(struct device *dev, void *res)
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
|
||||
static int devm_kzalloc_match(struct device *dev, void *res, void *data)
|
||||
{
|
||||
return res == data;
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_kzalloc - Managed kzalloc
|
||||
* @dev: Device to allocate memory for
|
||||
* @size: Allocation size
|
||||
* @gfp: Allocation gfp flags
|
||||
*
|
||||
* Managed kzalloc. Memory allocated with this function is
|
||||
* automatically freed on driver detach. Like all other devres
|
||||
* resources, guaranteed alignment is unsigned long long.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to allocated memory on success, NULL on failure.
|
||||
*/
|
||||
void * devm_kzalloc(struct device *dev, size_t size, gfp_t gfp)
|
||||
{
|
||||
struct devres *dr;
|
||||
|
||||
/* use raw alloc_dr for kmalloc caller tracing */
|
||||
dr = alloc_dr(devm_kzalloc_release, size, gfp);
|
||||
if (unlikely(!dr))
|
||||
return NULL;
|
||||
|
||||
set_node_dbginfo(&dr->node, "devm_kzalloc_release", size);
|
||||
devres_add(dev, dr->data);
|
||||
return dr->data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_kzalloc);
|
||||
|
||||
/**
|
||||
* devm_kfree - Managed kfree
|
||||
* @dev: Device this memory belongs to
|
||||
* @p: Memory to free
|
||||
*
|
||||
* Free memory allocated with dev_kzalloc().
|
||||
*/
|
||||
void devm_kfree(struct device *dev, void *p)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = devres_destroy(dev, devm_kzalloc_release, devm_kzalloc_match, p);
|
||||
WARN_ON(rc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_kfree);
|
||||
218
drivers/base/dma-mapping.c
Normal file
218
drivers/base/dma-mapping.c
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* drivers/base/dma-mapping.c - arch-independent dma-mapping routines
|
||||
*
|
||||
* Copyright (c) 2006 SUSE Linux Products GmbH
|
||||
* Copyright (c) 2006 Tejun Heo <teheo@suse.de>
|
||||
*
|
||||
* This file is released under the GPLv2.
|
||||
*/
|
||||
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
/*
|
||||
* Managed DMA API
|
||||
*/
|
||||
struct dma_devres {
|
||||
size_t size;
|
||||
void *vaddr;
|
||||
dma_addr_t dma_handle;
|
||||
};
|
||||
|
||||
static void dmam_coherent_release(struct device *dev, void *res)
|
||||
{
|
||||
struct dma_devres *this = res;
|
||||
|
||||
dma_free_coherent(dev, this->size, this->vaddr, this->dma_handle);
|
||||
}
|
||||
|
||||
static void dmam_noncoherent_release(struct device *dev, void *res)
|
||||
{
|
||||
struct dma_devres *this = res;
|
||||
|
||||
dma_free_noncoherent(dev, this->size, this->vaddr, this->dma_handle);
|
||||
}
|
||||
|
||||
static int dmam_match(struct device *dev, void *res, void *match_data)
|
||||
{
|
||||
struct dma_devres *this = res, *match = match_data;
|
||||
|
||||
if (this->vaddr == match->vaddr) {
|
||||
WARN_ON(this->size != match->size ||
|
||||
this->dma_handle != match->dma_handle);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dmam_alloc_coherent - Managed dma_alloc_coherent()
|
||||
* @dev: Device to allocate coherent memory for
|
||||
* @size: Size of allocation
|
||||
* @dma_handle: Out argument for allocated DMA handle
|
||||
* @gfp: Allocation flags
|
||||
*
|
||||
* Managed dma_alloc_coherent(). Memory allocated using this function
|
||||
* will be automatically released on driver detach.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to allocated memory on success, NULL on failure.
|
||||
*/
|
||||
void * dmam_alloc_coherent(struct device *dev, size_t size,
|
||||
dma_addr_t *dma_handle, gfp_t gfp)
|
||||
{
|
||||
struct dma_devres *dr;
|
||||
void *vaddr;
|
||||
|
||||
dr = devres_alloc(dmam_coherent_release, sizeof(*dr), gfp);
|
||||
if (!dr)
|
||||
return NULL;
|
||||
|
||||
vaddr = dma_alloc_coherent(dev, size, dma_handle, gfp);
|
||||
if (!vaddr) {
|
||||
devres_free(dr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dr->vaddr = vaddr;
|
||||
dr->dma_handle = *dma_handle;
|
||||
dr->size = size;
|
||||
|
||||
devres_add(dev, dr);
|
||||
|
||||
return vaddr;
|
||||
}
|
||||
EXPORT_SYMBOL(dmam_alloc_coherent);
|
||||
|
||||
/**
|
||||
* dmam_free_coherent - Managed dma_free_coherent()
|
||||
* @dev: Device to free coherent memory for
|
||||
* @size: Size of allocation
|
||||
* @vaddr: Virtual address of the memory to free
|
||||
* @dma_handle: DMA handle of the memory to free
|
||||
*
|
||||
* Managed dma_free_coherent().
|
||||
*/
|
||||
void dmam_free_coherent(struct device *dev, size_t size, void *vaddr,
|
||||
dma_addr_t dma_handle)
|
||||
{
|
||||
struct dma_devres match_data = { size, vaddr, dma_handle };
|
||||
|
||||
dma_free_coherent(dev, size, vaddr, dma_handle);
|
||||
WARN_ON(devres_destroy(dev, dmam_coherent_release, dmam_match,
|
||||
&match_data));
|
||||
}
|
||||
EXPORT_SYMBOL(dmam_free_coherent);
|
||||
|
||||
/**
|
||||
* dmam_alloc_non_coherent - Managed dma_alloc_non_coherent()
|
||||
* @dev: Device to allocate non_coherent memory for
|
||||
* @size: Size of allocation
|
||||
* @dma_handle: Out argument for allocated DMA handle
|
||||
* @gfp: Allocation flags
|
||||
*
|
||||
* Managed dma_alloc_non_coherent(). Memory allocated using this
|
||||
* function will be automatically released on driver detach.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to allocated memory on success, NULL on failure.
|
||||
*/
|
||||
void *dmam_alloc_noncoherent(struct device *dev, size_t size,
|
||||
dma_addr_t *dma_handle, gfp_t gfp)
|
||||
{
|
||||
struct dma_devres *dr;
|
||||
void *vaddr;
|
||||
|
||||
dr = devres_alloc(dmam_noncoherent_release, sizeof(*dr), gfp);
|
||||
if (!dr)
|
||||
return NULL;
|
||||
|
||||
vaddr = dma_alloc_noncoherent(dev, size, dma_handle, gfp);
|
||||
if (!vaddr) {
|
||||
devres_free(dr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dr->vaddr = vaddr;
|
||||
dr->dma_handle = *dma_handle;
|
||||
dr->size = size;
|
||||
|
||||
devres_add(dev, dr);
|
||||
|
||||
return vaddr;
|
||||
}
|
||||
EXPORT_SYMBOL(dmam_alloc_noncoherent);
|
||||
|
||||
/**
|
||||
* dmam_free_coherent - Managed dma_free_noncoherent()
|
||||
* @dev: Device to free noncoherent memory for
|
||||
* @size: Size of allocation
|
||||
* @vaddr: Virtual address of the memory to free
|
||||
* @dma_handle: DMA handle of the memory to free
|
||||
*
|
||||
* Managed dma_free_noncoherent().
|
||||
*/
|
||||
void dmam_free_noncoherent(struct device *dev, size_t size, void *vaddr,
|
||||
dma_addr_t dma_handle)
|
||||
{
|
||||
struct dma_devres match_data = { size, vaddr, dma_handle };
|
||||
|
||||
dma_free_noncoherent(dev, size, vaddr, dma_handle);
|
||||
WARN_ON(!devres_destroy(dev, dmam_noncoherent_release, dmam_match,
|
||||
&match_data));
|
||||
}
|
||||
EXPORT_SYMBOL(dmam_free_noncoherent);
|
||||
|
||||
#ifdef ARCH_HAS_DMA_DECLARE_COHERENT_MEMORY
|
||||
|
||||
static void dmam_coherent_decl_release(struct device *dev, void *res)
|
||||
{
|
||||
dma_release_declared_memory(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* dmam_declare_coherent_memory - Managed dma_declare_coherent_memory()
|
||||
* @dev: Device to declare coherent memory for
|
||||
* @bus_addr: Bus address of coherent memory to be declared
|
||||
* @device_addr: Device address of coherent memory to be declared
|
||||
* @size: Size of coherent memory to be declared
|
||||
* @flags: Flags
|
||||
*
|
||||
* Managed dma_declare_coherent_memory().
|
||||
*
|
||||
* RETURNS:
|
||||
* 0 on success, -errno on failure.
|
||||
*/
|
||||
int dmam_declare_coherent_memory(struct device *dev, dma_addr_t bus_addr,
|
||||
dma_addr_t device_addr, size_t size, int flags)
|
||||
{
|
||||
void *res;
|
||||
int rc;
|
||||
|
||||
res = devres_alloc(dmam_coherent_decl_release, 0, GFP_KERNEL);
|
||||
if (!res)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = dma_declare_coherent_memory(dev, bus_addr, device_addr, size,
|
||||
flags);
|
||||
if (rc == 0)
|
||||
devres_add(dev, res);
|
||||
else
|
||||
devres_free(res);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL(dmam_declare_coherent_memory);
|
||||
|
||||
/**
|
||||
* dmam_release_declared_memory - Managed dma_release_declared_memory().
|
||||
* @dev: Device to release declared coherent memory for
|
||||
*
|
||||
* Managed dmam_release_declared_memory().
|
||||
*/
|
||||
void dmam_release_declared_memory(struct device *dev)
|
||||
{
|
||||
WARN_ON(devres_destroy(dev, dmam_coherent_decl_release, NULL, NULL));
|
||||
}
|
||||
EXPORT_SYMBOL(dmam_release_declared_memory);
|
||||
|
||||
#endif
|
||||
481
drivers/base/dmapool.c
Normal file
481
drivers/base/dmapool.c
Normal file
@@ -0,0 +1,481 @@
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/mm.h>
|
||||
#include <asm/io.h> /* Needed for i386 to build */
|
||||
#include <asm/scatterlist.h> /* Needed for i386 to build */
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmapool.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/poison.h>
|
||||
|
||||
/*
|
||||
* Pool allocator ... wraps the dma_alloc_coherent page allocator, so
|
||||
* small blocks are easily used by drivers for bus mastering controllers.
|
||||
* This should probably be sharing the guts of the slab allocator.
|
||||
*/
|
||||
|
||||
struct dma_pool { /* the pool */
|
||||
struct list_head page_list;
|
||||
spinlock_t lock;
|
||||
size_t blocks_per_page;
|
||||
size_t size;
|
||||
struct device *dev;
|
||||
size_t allocation;
|
||||
char name [32];
|
||||
wait_queue_head_t waitq;
|
||||
struct list_head pools;
|
||||
};
|
||||
|
||||
struct dma_page { /* cacheable header for 'allocation' bytes */
|
||||
struct list_head page_list;
|
||||
void *vaddr;
|
||||
dma_addr_t dma;
|
||||
unsigned in_use;
|
||||
unsigned long bitmap [0];
|
||||
};
|
||||
|
||||
#define POOL_TIMEOUT_JIFFIES ((100 /* msec */ * HZ) / 1000)
|
||||
|
||||
static DECLARE_MUTEX (pools_lock);
|
||||
|
||||
static ssize_t
|
||||
show_pools (struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned temp;
|
||||
unsigned size;
|
||||
char *next;
|
||||
struct dma_page *page;
|
||||
struct dma_pool *pool;
|
||||
|
||||
next = buf;
|
||||
size = PAGE_SIZE;
|
||||
|
||||
temp = scnprintf(next, size, "poolinfo - 0.1\n");
|
||||
size -= temp;
|
||||
next += temp;
|
||||
|
||||
down (&pools_lock);
|
||||
list_for_each_entry(pool, &dev->dma_pools, pools) {
|
||||
unsigned pages = 0;
|
||||
unsigned blocks = 0;
|
||||
|
||||
list_for_each_entry(page, &pool->page_list, page_list) {
|
||||
pages++;
|
||||
blocks += page->in_use;
|
||||
}
|
||||
|
||||
/* per-pool info, no real statistics yet */
|
||||
temp = scnprintf(next, size, "%-16s %4u %4Zu %4Zu %2u\n",
|
||||
pool->name,
|
||||
blocks, pages * pool->blocks_per_page,
|
||||
pool->size, pages);
|
||||
size -= temp;
|
||||
next += temp;
|
||||
}
|
||||
up (&pools_lock);
|
||||
|
||||
return PAGE_SIZE - size;
|
||||
}
|
||||
static DEVICE_ATTR (pools, S_IRUGO, show_pools, NULL);
|
||||
|
||||
/**
|
||||
* dma_pool_create - Creates a pool of consistent memory blocks, for dma.
|
||||
* @name: name of pool, for diagnostics
|
||||
* @dev: device that will be doing the DMA
|
||||
* @size: size of the blocks in this pool.
|
||||
* @align: alignment requirement for blocks; must be a power of two
|
||||
* @allocation: returned blocks won't cross this boundary (or zero)
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Returns a dma allocation pool with the requested characteristics, or
|
||||
* null if one can't be created. Given one of these pools, dma_pool_alloc()
|
||||
* may be used to allocate memory. Such memory will all have "consistent"
|
||||
* DMA mappings, accessible by the device and its driver without using
|
||||
* cache flushing primitives. The actual size of blocks allocated may be
|
||||
* larger than requested because of alignment.
|
||||
*
|
||||
* If allocation is nonzero, objects returned from dma_pool_alloc() won't
|
||||
* cross that size boundary. This is useful for devices which have
|
||||
* addressing restrictions on individual DMA transfers, such as not crossing
|
||||
* boundaries of 4KBytes.
|
||||
*/
|
||||
struct dma_pool *
|
||||
dma_pool_create (const char *name, struct device *dev,
|
||||
size_t size, size_t align, size_t allocation)
|
||||
{
|
||||
struct dma_pool *retval;
|
||||
|
||||
if (align == 0)
|
||||
align = 1;
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
else if (size < align)
|
||||
size = align;
|
||||
else if ((size % align) != 0) {
|
||||
size += align + 1;
|
||||
size &= ~(align - 1);
|
||||
}
|
||||
|
||||
if (allocation == 0) {
|
||||
if (PAGE_SIZE < size)
|
||||
allocation = size;
|
||||
else
|
||||
allocation = PAGE_SIZE;
|
||||
// FIXME: round up for less fragmentation
|
||||
} else if (allocation < size)
|
||||
return NULL;
|
||||
|
||||
if (!(retval = kmalloc (sizeof *retval, GFP_KERNEL)))
|
||||
return retval;
|
||||
|
||||
strlcpy (retval->name, name, sizeof retval->name);
|
||||
|
||||
retval->dev = dev;
|
||||
|
||||
INIT_LIST_HEAD (&retval->page_list);
|
||||
spin_lock_init (&retval->lock);
|
||||
retval->size = size;
|
||||
retval->allocation = allocation;
|
||||
retval->blocks_per_page = allocation / size;
|
||||
init_waitqueue_head (&retval->waitq);
|
||||
|
||||
if (dev) {
|
||||
int ret;
|
||||
|
||||
down (&pools_lock);
|
||||
if (list_empty (&dev->dma_pools))
|
||||
ret = device_create_file (dev, &dev_attr_pools);
|
||||
else
|
||||
ret = 0;
|
||||
/* note: not currently insisting "name" be unique */
|
||||
if (!ret)
|
||||
list_add (&retval->pools, &dev->dma_pools);
|
||||
else {
|
||||
kfree(retval);
|
||||
retval = NULL;
|
||||
}
|
||||
up (&pools_lock);
|
||||
} else
|
||||
INIT_LIST_HEAD (&retval->pools);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static struct dma_page *
|
||||
pool_alloc_page (struct dma_pool *pool, gfp_t mem_flags)
|
||||
{
|
||||
struct dma_page *page;
|
||||
int mapsize;
|
||||
|
||||
mapsize = pool->blocks_per_page;
|
||||
mapsize = (mapsize + BITS_PER_LONG - 1) / BITS_PER_LONG;
|
||||
mapsize *= sizeof (long);
|
||||
|
||||
page = kmalloc(mapsize + sizeof *page, mem_flags);
|
||||
if (!page)
|
||||
return NULL;
|
||||
page->vaddr = dma_alloc_coherent (pool->dev,
|
||||
pool->allocation,
|
||||
&page->dma,
|
||||
mem_flags);
|
||||
if (page->vaddr) {
|
||||
memset (page->bitmap, 0xff, mapsize); // bit set == free
|
||||
#ifdef CONFIG_DEBUG_SLAB
|
||||
memset (page->vaddr, POOL_POISON_FREED, pool->allocation);
|
||||
#endif
|
||||
list_add (&page->page_list, &pool->page_list);
|
||||
page->in_use = 0;
|
||||
} else {
|
||||
kfree (page);
|
||||
page = NULL;
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
|
||||
static inline int
|
||||
is_page_busy (int blocks, unsigned long *bitmap)
|
||||
{
|
||||
while (blocks > 0) {
|
||||
if (*bitmap++ != ~0UL)
|
||||
return 1;
|
||||
blocks -= BITS_PER_LONG;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
pool_free_page (struct dma_pool *pool, struct dma_page *page)
|
||||
{
|
||||
dma_addr_t dma = page->dma;
|
||||
|
||||
#ifdef CONFIG_DEBUG_SLAB
|
||||
memset (page->vaddr, POOL_POISON_FREED, pool->allocation);
|
||||
#endif
|
||||
dma_free_coherent (pool->dev, pool->allocation, page->vaddr, dma);
|
||||
list_del (&page->page_list);
|
||||
kfree (page);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dma_pool_destroy - destroys a pool of dma memory blocks.
|
||||
* @pool: dma pool that will be destroyed
|
||||
* Context: !in_interrupt()
|
||||
*
|
||||
* Caller guarantees that no more memory from the pool is in use,
|
||||
* and that nothing will try to use the pool after this call.
|
||||
*/
|
||||
void
|
||||
dma_pool_destroy (struct dma_pool *pool)
|
||||
{
|
||||
down (&pools_lock);
|
||||
list_del (&pool->pools);
|
||||
if (pool->dev && list_empty (&pool->dev->dma_pools))
|
||||
device_remove_file (pool->dev, &dev_attr_pools);
|
||||
up (&pools_lock);
|
||||
|
||||
while (!list_empty (&pool->page_list)) {
|
||||
struct dma_page *page;
|
||||
page = list_entry (pool->page_list.next,
|
||||
struct dma_page, page_list);
|
||||
if (is_page_busy (pool->blocks_per_page, page->bitmap)) {
|
||||
if (pool->dev)
|
||||
dev_err(pool->dev, "dma_pool_destroy %s, %p busy\n",
|
||||
pool->name, page->vaddr);
|
||||
else
|
||||
printk (KERN_ERR "dma_pool_destroy %s, %p busy\n",
|
||||
pool->name, page->vaddr);
|
||||
/* leak the still-in-use consistent memory */
|
||||
list_del (&page->page_list);
|
||||
kfree (page);
|
||||
} else
|
||||
pool_free_page (pool, page);
|
||||
}
|
||||
|
||||
kfree (pool);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dma_pool_alloc - get a block of consistent memory
|
||||
* @pool: dma pool that will produce the block
|
||||
* @mem_flags: GFP_* bitmask
|
||||
* @handle: pointer to dma address of block
|
||||
*
|
||||
* This returns the kernel virtual address of a currently unused block,
|
||||
* and reports its dma address through the handle.
|
||||
* If such a memory block can't be allocated, null is returned.
|
||||
*/
|
||||
void *
|
||||
dma_pool_alloc (struct dma_pool *pool, gfp_t mem_flags, dma_addr_t *handle)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct dma_page *page;
|
||||
int map, block;
|
||||
size_t offset;
|
||||
void *retval;
|
||||
|
||||
restart:
|
||||
spin_lock_irqsave (&pool->lock, flags);
|
||||
list_for_each_entry(page, &pool->page_list, page_list) {
|
||||
int i;
|
||||
/* only cachable accesses here ... */
|
||||
for (map = 0, i = 0;
|
||||
i < pool->blocks_per_page;
|
||||
i += BITS_PER_LONG, map++) {
|
||||
if (page->bitmap [map] == 0)
|
||||
continue;
|
||||
block = ffz (~ page->bitmap [map]);
|
||||
if ((i + block) < pool->blocks_per_page) {
|
||||
clear_bit (block, &page->bitmap [map]);
|
||||
offset = (BITS_PER_LONG * map) + block;
|
||||
offset *= pool->size;
|
||||
goto ready;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(page = pool_alloc_page (pool, GFP_ATOMIC))) {
|
||||
if (mem_flags & __GFP_WAIT) {
|
||||
DECLARE_WAITQUEUE (wait, current);
|
||||
|
||||
current->state = TASK_INTERRUPTIBLE;
|
||||
add_wait_queue (&pool->waitq, &wait);
|
||||
spin_unlock_irqrestore (&pool->lock, flags);
|
||||
|
||||
schedule_timeout (POOL_TIMEOUT_JIFFIES);
|
||||
|
||||
remove_wait_queue (&pool->waitq, &wait);
|
||||
goto restart;
|
||||
}
|
||||
retval = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
clear_bit (0, &page->bitmap [0]);
|
||||
offset = 0;
|
||||
ready:
|
||||
page->in_use++;
|
||||
retval = offset + page->vaddr;
|
||||
*handle = offset + page->dma;
|
||||
#ifdef CONFIG_DEBUG_SLAB
|
||||
memset (retval, POOL_POISON_ALLOCATED, pool->size);
|
||||
#endif
|
||||
done:
|
||||
spin_unlock_irqrestore (&pool->lock, flags);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static struct dma_page *
|
||||
pool_find_page (struct dma_pool *pool, dma_addr_t dma)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct dma_page *page;
|
||||
|
||||
spin_lock_irqsave (&pool->lock, flags);
|
||||
list_for_each_entry(page, &pool->page_list, page_list) {
|
||||
if (dma < page->dma)
|
||||
continue;
|
||||
if (dma < (page->dma + pool->allocation))
|
||||
goto done;
|
||||
}
|
||||
page = NULL;
|
||||
done:
|
||||
spin_unlock_irqrestore (&pool->lock, flags);
|
||||
return page;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dma_pool_free - put block back into dma pool
|
||||
* @pool: the dma pool holding the block
|
||||
* @vaddr: virtual address of block
|
||||
* @dma: dma address of block
|
||||
*
|
||||
* Caller promises neither device nor driver will again touch this block
|
||||
* unless it is first re-allocated.
|
||||
*/
|
||||
void
|
||||
dma_pool_free (struct dma_pool *pool, void *vaddr, dma_addr_t dma)
|
||||
{
|
||||
struct dma_page *page;
|
||||
unsigned long flags;
|
||||
int map, block;
|
||||
|
||||
if ((page = pool_find_page (pool, dma)) == 0) {
|
||||
if (pool->dev)
|
||||
dev_err(pool->dev, "dma_pool_free %s, %p/%lx (bad dma)\n",
|
||||
pool->name, vaddr, (unsigned long) dma);
|
||||
else
|
||||
printk (KERN_ERR "dma_pool_free %s, %p/%lx (bad dma)\n",
|
||||
pool->name, vaddr, (unsigned long) dma);
|
||||
return;
|
||||
}
|
||||
|
||||
block = dma - page->dma;
|
||||
block /= pool->size;
|
||||
map = block / BITS_PER_LONG;
|
||||
block %= BITS_PER_LONG;
|
||||
|
||||
#ifdef CONFIG_DEBUG_SLAB
|
||||
if (((dma - page->dma) + (void *)page->vaddr) != vaddr) {
|
||||
if (pool->dev)
|
||||
dev_err(pool->dev, "dma_pool_free %s, %p (bad vaddr)/%Lx\n",
|
||||
pool->name, vaddr, (unsigned long long) dma);
|
||||
else
|
||||
printk (KERN_ERR "dma_pool_free %s, %p (bad vaddr)/%Lx\n",
|
||||
pool->name, vaddr, (unsigned long long) dma);
|
||||
return;
|
||||
}
|
||||
if (page->bitmap [map] & (1UL << block)) {
|
||||
if (pool->dev)
|
||||
dev_err(pool->dev, "dma_pool_free %s, dma %Lx already free\n",
|
||||
pool->name, (unsigned long long)dma);
|
||||
else
|
||||
printk (KERN_ERR "dma_pool_free %s, dma %Lx already free\n",
|
||||
pool->name, (unsigned long long)dma);
|
||||
return;
|
||||
}
|
||||
memset (vaddr, POOL_POISON_FREED, pool->size);
|
||||
#endif
|
||||
|
||||
spin_lock_irqsave (&pool->lock, flags);
|
||||
page->in_use--;
|
||||
set_bit (block, &page->bitmap [map]);
|
||||
if (waitqueue_active (&pool->waitq))
|
||||
wake_up (&pool->waitq);
|
||||
/*
|
||||
* Resist a temptation to do
|
||||
* if (!is_page_busy(bpp, page->bitmap)) pool_free_page(pool, page);
|
||||
* Better have a few empty pages hang around.
|
||||
*/
|
||||
spin_unlock_irqrestore (&pool->lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Managed DMA pool
|
||||
*/
|
||||
static void dmam_pool_release(struct device *dev, void *res)
|
||||
{
|
||||
struct dma_pool *pool = *(struct dma_pool **)res;
|
||||
|
||||
dma_pool_destroy(pool);
|
||||
}
|
||||
|
||||
static int dmam_pool_match(struct device *dev, void *res, void *match_data)
|
||||
{
|
||||
return *(struct dma_pool **)res == match_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* dmam_pool_create - Managed dma_pool_create()
|
||||
* @name: name of pool, for diagnostics
|
||||
* @dev: device that will be doing the DMA
|
||||
* @size: size of the blocks in this pool.
|
||||
* @align: alignment requirement for blocks; must be a power of two
|
||||
* @allocation: returned blocks won't cross this boundary (or zero)
|
||||
*
|
||||
* Managed dma_pool_create(). DMA pool created with this function is
|
||||
* automatically destroyed on driver detach.
|
||||
*/
|
||||
struct dma_pool *dmam_pool_create(const char *name, struct device *dev,
|
||||
size_t size, size_t align, size_t allocation)
|
||||
{
|
||||
struct dma_pool **ptr, *pool;
|
||||
|
||||
ptr = devres_alloc(dmam_pool_release, sizeof(*ptr), GFP_KERNEL);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
|
||||
pool = *ptr = dma_pool_create(name, dev, size, align, allocation);
|
||||
if (pool)
|
||||
devres_add(dev, ptr);
|
||||
else
|
||||
devres_free(ptr);
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* dmam_pool_destroy - Managed dma_pool_destroy()
|
||||
* @pool: dma pool that will be destroyed
|
||||
*
|
||||
* Managed dma_pool_destroy().
|
||||
*/
|
||||
void dmam_pool_destroy(struct dma_pool *pool)
|
||||
{
|
||||
struct device *dev = pool->dev;
|
||||
|
||||
dma_pool_destroy(pool);
|
||||
WARN_ON(devres_destroy(dev, dmam_pool_release, dmam_pool_match, pool));
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL (dma_pool_create);
|
||||
EXPORT_SYMBOL (dma_pool_destroy);
|
||||
EXPORT_SYMBOL (dma_pool_alloc);
|
||||
EXPORT_SYMBOL (dma_pool_free);
|
||||
EXPORT_SYMBOL (dmam_pool_create);
|
||||
EXPORT_SYMBOL (dmam_pool_destroy);
|
||||
221
drivers/base/driver.c
Normal file
221
drivers/base/driver.c
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* driver.c - centralized device driver management
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* Copyright (c) 2002-3 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include "base.h"
|
||||
|
||||
#define to_dev(node) container_of(node, struct device, driver_list)
|
||||
#define to_drv(obj) container_of(obj, struct device_driver, kobj)
|
||||
|
||||
|
||||
static struct device * next_device(struct klist_iter * i)
|
||||
{
|
||||
struct klist_node * n = klist_next(i);
|
||||
return n ? container_of(n, struct device, knode_driver) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* driver_for_each_device - Iterator for devices bound to a driver.
|
||||
* @drv: Driver we're iterating.
|
||||
* @start: Device to begin with
|
||||
* @data: Data to pass to the callback.
|
||||
* @fn: Function to call for each device.
|
||||
*
|
||||
* Iterate over the @drv's list of devices calling @fn for each one.
|
||||
*/
|
||||
|
||||
int driver_for_each_device(struct device_driver * drv, struct device * start,
|
||||
void * data, int (*fn)(struct device *, void *))
|
||||
{
|
||||
struct klist_iter i;
|
||||
struct device * dev;
|
||||
int error = 0;
|
||||
|
||||
if (!drv)
|
||||
return -EINVAL;
|
||||
|
||||
klist_iter_init_node(&drv->klist_devices, &i,
|
||||
start ? &start->knode_driver : NULL);
|
||||
while ((dev = next_device(&i)) && !error)
|
||||
error = fn(dev, data);
|
||||
klist_iter_exit(&i);
|
||||
return error;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(driver_for_each_device);
|
||||
|
||||
|
||||
/**
|
||||
* driver_find_device - device iterator for locating a particular device.
|
||||
* @drv: The device's driver
|
||||
* @start: Device to begin with
|
||||
* @data: Data to pass to match function
|
||||
* @match: Callback function to check device
|
||||
*
|
||||
* This is similar to the driver_for_each_device() function above, but
|
||||
* it returns a reference to a device that is 'found' for later use, as
|
||||
* determined by the @match callback.
|
||||
*
|
||||
* The callback should return 0 if the device doesn't match and non-zero
|
||||
* if it does. If the callback returns non-zero, this function will
|
||||
* return to the caller and not iterate over any more devices.
|
||||
*/
|
||||
struct device * driver_find_device(struct device_driver *drv,
|
||||
struct device * start, void * data,
|
||||
int (*match)(struct device *, void *))
|
||||
{
|
||||
struct klist_iter i;
|
||||
struct device *dev;
|
||||
|
||||
if (!drv)
|
||||
return NULL;
|
||||
|
||||
klist_iter_init_node(&drv->klist_devices, &i,
|
||||
(start ? &start->knode_driver : NULL));
|
||||
while ((dev = next_device(&i)))
|
||||
if (match(dev, data) && get_device(dev))
|
||||
break;
|
||||
klist_iter_exit(&i);
|
||||
return dev;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(driver_find_device);
|
||||
|
||||
/**
|
||||
* driver_create_file - create sysfs file for driver.
|
||||
* @drv: driver.
|
||||
* @attr: driver attribute descriptor.
|
||||
*/
|
||||
|
||||
int driver_create_file(struct device_driver * drv, struct driver_attribute * attr)
|
||||
{
|
||||
int error;
|
||||
if (get_driver(drv)) {
|
||||
error = sysfs_create_file(&drv->kobj, &attr->attr);
|
||||
put_driver(drv);
|
||||
} else
|
||||
error = -EINVAL;
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* driver_remove_file - remove sysfs file for driver.
|
||||
* @drv: driver.
|
||||
* @attr: driver attribute descriptor.
|
||||
*/
|
||||
|
||||
void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr)
|
||||
{
|
||||
if (get_driver(drv)) {
|
||||
sysfs_remove_file(&drv->kobj, &attr->attr);
|
||||
put_driver(drv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get_driver - increment driver reference count.
|
||||
* @drv: driver.
|
||||
*/
|
||||
struct device_driver * get_driver(struct device_driver * drv)
|
||||
{
|
||||
return drv ? to_drv(kobject_get(&drv->kobj)) : NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* put_driver - decrement driver's refcount.
|
||||
* @drv: driver.
|
||||
*/
|
||||
void put_driver(struct device_driver * drv)
|
||||
{
|
||||
kobject_put(&drv->kobj);
|
||||
}
|
||||
|
||||
/**
|
||||
* driver_register - register driver with bus
|
||||
* @drv: driver to register
|
||||
*
|
||||
* We pass off most of the work to the bus_add_driver() call,
|
||||
* since most of the things we have to do deal with the bus
|
||||
* structures.
|
||||
*
|
||||
* The one interesting aspect is that we setup @drv->unloaded
|
||||
* as a completion that gets complete when the driver reference
|
||||
* count reaches 0.
|
||||
*/
|
||||
int driver_register(struct device_driver * drv)
|
||||
{
|
||||
if ((drv->bus->probe && drv->probe) ||
|
||||
(drv->bus->remove && drv->remove) ||
|
||||
(drv->bus->shutdown && drv->shutdown)) {
|
||||
printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name);
|
||||
}
|
||||
klist_init(&drv->klist_devices, NULL, NULL);
|
||||
init_completion(&drv->unloaded);
|
||||
return bus_add_driver(drv);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* driver_unregister - remove driver from system.
|
||||
* @drv: driver.
|
||||
*
|
||||
* Again, we pass off most of the work to the bus-level call.
|
||||
*
|
||||
* Though, once that is done, we wait until @drv->unloaded is completed.
|
||||
* This will block until the driver refcount reaches 0, and it is
|
||||
* released. Only modular drivers will call this function, and we
|
||||
* have to guarantee that it won't complete, letting the driver
|
||||
* unload until all references are gone.
|
||||
*/
|
||||
|
||||
void driver_unregister(struct device_driver * drv)
|
||||
{
|
||||
bus_remove_driver(drv);
|
||||
/*
|
||||
* If the driver is a module, we are probably in
|
||||
* the module unload path, and we want to wait
|
||||
* for everything to unload before we can actually
|
||||
* finish the unload.
|
||||
*/
|
||||
if (drv->owner)
|
||||
wait_for_completion(&drv->unloaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* driver_find - locate driver on a bus by its name.
|
||||
* @name: name of the driver.
|
||||
* @bus: bus to scan for the driver.
|
||||
*
|
||||
* Call kset_find_obj() to iterate over list of drivers on
|
||||
* a bus to find driver by name. Return driver if found.
|
||||
*
|
||||
* Note that kset_find_obj increments driver's reference count.
|
||||
*/
|
||||
struct device_driver *driver_find(const char *name, struct bus_type *bus)
|
||||
{
|
||||
struct kobject *k = kset_find_obj(&bus->drivers, name);
|
||||
if (k)
|
||||
return to_drv(k);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(driver_register);
|
||||
EXPORT_SYMBOL_GPL(driver_unregister);
|
||||
EXPORT_SYMBOL_GPL(get_driver);
|
||||
EXPORT_SYMBOL_GPL(put_driver);
|
||||
EXPORT_SYMBOL_GPL(driver_find);
|
||||
|
||||
EXPORT_SYMBOL_GPL(driver_create_file);
|
||||
EXPORT_SYMBOL_GPL(driver_remove_file);
|
||||
37
drivers/base/firmware.c
Normal file
37
drivers/base/firmware.c
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* firmware.c - firmware subsystem hoohaw.
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* Copyright (c) 2002-3 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
static decl_subsys(firmware, NULL, NULL);
|
||||
|
||||
int firmware_register(struct subsystem * s)
|
||||
{
|
||||
kset_set_kset_s(s, firmware_subsys);
|
||||
return subsystem_register(s);
|
||||
}
|
||||
|
||||
void firmware_unregister(struct subsystem * s)
|
||||
{
|
||||
subsystem_unregister(s);
|
||||
}
|
||||
|
||||
int __init firmware_init(void)
|
||||
{
|
||||
return subsystem_register(&firmware_subsys);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(firmware_register);
|
||||
EXPORT_SYMBOL_GPL(firmware_unregister);
|
||||
606
drivers/base/firmware_class.c
Normal file
606
drivers/base/firmware_class.c
Normal file
@@ -0,0 +1,606 @@
|
||||
/*
|
||||
* firmware_class.c - Multi purpose firmware loading support
|
||||
*
|
||||
* Copyright (c) 2003 Manuel Estrada Sainz <ranty@debian.org>
|
||||
*
|
||||
* Please see Documentation/firmware_class/ for more information.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/capability.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/kthread.h>
|
||||
|
||||
#include <linux/firmware.h>
|
||||
#include "base.h"
|
||||
|
||||
#define to_dev(obj) container_of(obj, struct device, kobj)
|
||||
|
||||
MODULE_AUTHOR("Manuel Estrada Sainz <ranty@debian.org>");
|
||||
MODULE_DESCRIPTION("Multi purpose firmware loading support");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
enum {
|
||||
FW_STATUS_LOADING,
|
||||
FW_STATUS_DONE,
|
||||
FW_STATUS_ABORT,
|
||||
FW_STATUS_READY,
|
||||
FW_STATUS_READY_NOHOTPLUG,
|
||||
};
|
||||
|
||||
static int loading_timeout = 60; /* In seconds */
|
||||
|
||||
/* fw_lock could be moved to 'struct firmware_priv' but since it is just
|
||||
* guarding for corner cases a global lock should be OK */
|
||||
static DEFINE_MUTEX(fw_lock);
|
||||
|
||||
struct firmware_priv {
|
||||
char fw_id[FIRMWARE_NAME_MAX];
|
||||
struct completion completion;
|
||||
struct bin_attribute attr_data;
|
||||
struct firmware *fw;
|
||||
unsigned long status;
|
||||
int alloc_size;
|
||||
struct timer_list timeout;
|
||||
};
|
||||
|
||||
static void
|
||||
fw_load_abort(struct firmware_priv *fw_priv)
|
||||
{
|
||||
set_bit(FW_STATUS_ABORT, &fw_priv->status);
|
||||
wmb();
|
||||
complete(&fw_priv->completion);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
firmware_timeout_show(struct class *class, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", loading_timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_timeout_store - set number of seconds to wait for firmware
|
||||
* @class: device class pointer
|
||||
* @buf: buffer to scan for timeout value
|
||||
* @count: number of bytes in @buf
|
||||
*
|
||||
* Sets the number of seconds to wait for the firmware. Once
|
||||
* this expires an error will be returned to the driver and no
|
||||
* firmware will be provided.
|
||||
*
|
||||
* Note: zero means 'wait forever'.
|
||||
**/
|
||||
static ssize_t
|
||||
firmware_timeout_store(struct class *class, const char *buf, size_t count)
|
||||
{
|
||||
loading_timeout = simple_strtol(buf, NULL, 10);
|
||||
if (loading_timeout < 0)
|
||||
loading_timeout = 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
static CLASS_ATTR(timeout, 0644, firmware_timeout_show, firmware_timeout_store);
|
||||
|
||||
static void fw_dev_release(struct device *dev);
|
||||
|
||||
static int firmware_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
|
||||
int i = 0, len = 0;
|
||||
|
||||
if (!test_bit(FW_STATUS_READY, &fw_priv->status))
|
||||
return -ENODEV;
|
||||
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &len,
|
||||
"FIRMWARE=%s", fw_priv->fw_id))
|
||||
return -ENOMEM;
|
||||
if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &len,
|
||||
"TIMEOUT=%i", loading_timeout))
|
||||
return -ENOMEM;
|
||||
envp[i] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct class firmware_class = {
|
||||
.name = "firmware",
|
||||
.dev_uevent = firmware_uevent,
|
||||
.dev_release = fw_dev_release,
|
||||
};
|
||||
|
||||
static ssize_t firmware_loading_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
|
||||
int loading = test_bit(FW_STATUS_LOADING, &fw_priv->status);
|
||||
return sprintf(buf, "%d\n", loading);
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_loading_store - set value in the 'loading' control file
|
||||
* @dev: device pointer
|
||||
* @attr: device attribute pointer
|
||||
* @buf: buffer to scan for loading control value
|
||||
* @count: number of bytes in @buf
|
||||
*
|
||||
* The relevant values are:
|
||||
*
|
||||
* 1: Start a load, discarding any previous partial load.
|
||||
* 0: Conclude the load and hand the data to the driver code.
|
||||
* -1: Conclude the load with an error and discard any written data.
|
||||
**/
|
||||
static ssize_t firmware_loading_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
|
||||
int loading = simple_strtol(buf, NULL, 10);
|
||||
|
||||
switch (loading) {
|
||||
case 1:
|
||||
mutex_lock(&fw_lock);
|
||||
if (!fw_priv->fw) {
|
||||
mutex_unlock(&fw_lock);
|
||||
break;
|
||||
}
|
||||
vfree(fw_priv->fw->data);
|
||||
fw_priv->fw->data = NULL;
|
||||
fw_priv->fw->size = 0;
|
||||
fw_priv->alloc_size = 0;
|
||||
set_bit(FW_STATUS_LOADING, &fw_priv->status);
|
||||
mutex_unlock(&fw_lock);
|
||||
break;
|
||||
case 0:
|
||||
if (test_bit(FW_STATUS_LOADING, &fw_priv->status)) {
|
||||
complete(&fw_priv->completion);
|
||||
clear_bit(FW_STATUS_LOADING, &fw_priv->status);
|
||||
break;
|
||||
}
|
||||
/* fallthrough */
|
||||
default:
|
||||
printk(KERN_ERR "%s: unexpected value (%d)\n", __FUNCTION__,
|
||||
loading);
|
||||
/* fallthrough */
|
||||
case -1:
|
||||
fw_load_abort(fw_priv);
|
||||
break;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
|
||||
|
||||
static ssize_t
|
||||
firmware_data_read(struct kobject *kobj,
|
||||
char *buffer, loff_t offset, size_t count)
|
||||
{
|
||||
struct device *dev = to_dev(kobj);
|
||||
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
|
||||
struct firmware *fw;
|
||||
ssize_t ret_count = count;
|
||||
|
||||
mutex_lock(&fw_lock);
|
||||
fw = fw_priv->fw;
|
||||
if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
|
||||
ret_count = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
if (offset > fw->size) {
|
||||
ret_count = 0;
|
||||
goto out;
|
||||
}
|
||||
if (offset + ret_count > fw->size)
|
||||
ret_count = fw->size - offset;
|
||||
|
||||
memcpy(buffer, fw->data + offset, ret_count);
|
||||
out:
|
||||
mutex_unlock(&fw_lock);
|
||||
return ret_count;
|
||||
}
|
||||
|
||||
static int
|
||||
fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size)
|
||||
{
|
||||
u8 *new_data;
|
||||
int new_size = fw_priv->alloc_size;
|
||||
|
||||
if (min_size <= fw_priv->alloc_size)
|
||||
return 0;
|
||||
|
||||
new_size = ALIGN(min_size, PAGE_SIZE);
|
||||
new_data = vmalloc(new_size);
|
||||
if (!new_data) {
|
||||
printk(KERN_ERR "%s: unable to alloc buffer\n", __FUNCTION__);
|
||||
/* Make sure that we don't keep incomplete data */
|
||||
fw_load_abort(fw_priv);
|
||||
return -ENOMEM;
|
||||
}
|
||||
fw_priv->alloc_size = new_size;
|
||||
if (fw_priv->fw->data) {
|
||||
memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size);
|
||||
vfree(fw_priv->fw->data);
|
||||
}
|
||||
fw_priv->fw->data = new_data;
|
||||
BUG_ON(min_size > fw_priv->alloc_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* firmware_data_write - write method for firmware
|
||||
* @kobj: kobject for the device
|
||||
* @buffer: buffer being written
|
||||
* @offset: buffer offset for write in total data store area
|
||||
* @count: buffer size
|
||||
*
|
||||
* Data written to the 'data' attribute will be later handed to
|
||||
* the driver as a firmware image.
|
||||
**/
|
||||
static ssize_t
|
||||
firmware_data_write(struct kobject *kobj,
|
||||
char *buffer, loff_t offset, size_t count)
|
||||
{
|
||||
struct device *dev = to_dev(kobj);
|
||||
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
|
||||
struct firmware *fw;
|
||||
ssize_t retval;
|
||||
|
||||
if (!capable(CAP_SYS_RAWIO))
|
||||
return -EPERM;
|
||||
|
||||
mutex_lock(&fw_lock);
|
||||
fw = fw_priv->fw;
|
||||
if (!fw || test_bit(FW_STATUS_DONE, &fw_priv->status)) {
|
||||
retval = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
retval = fw_realloc_buffer(fw_priv, offset + count);
|
||||
if (retval)
|
||||
goto out;
|
||||
|
||||
memcpy(fw->data + offset, buffer, count);
|
||||
|
||||
fw->size = max_t(size_t, offset + count, fw->size);
|
||||
retval = count;
|
||||
out:
|
||||
mutex_unlock(&fw_lock);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct bin_attribute firmware_attr_data_tmpl = {
|
||||
.attr = {.name = "data", .mode = 0644, .owner = THIS_MODULE},
|
||||
.size = 0,
|
||||
.read = firmware_data_read,
|
||||
.write = firmware_data_write,
|
||||
};
|
||||
|
||||
static void fw_dev_release(struct device *dev)
|
||||
{
|
||||
struct firmware_priv *fw_priv = dev_get_drvdata(dev);
|
||||
|
||||
kfree(fw_priv);
|
||||
kfree(dev);
|
||||
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
static void
|
||||
firmware_class_timeout(u_long data)
|
||||
{
|
||||
struct firmware_priv *fw_priv = (struct firmware_priv *) data;
|
||||
fw_load_abort(fw_priv);
|
||||
}
|
||||
|
||||
static inline void fw_setup_device_id(struct device *f_dev, struct device *dev)
|
||||
{
|
||||
/* XXX warning we should watch out for name collisions */
|
||||
strlcpy(f_dev->bus_id, dev->bus_id, BUS_ID_SIZE);
|
||||
}
|
||||
|
||||
static int fw_register_device(struct device **dev_p, const char *fw_name,
|
||||
struct device *device)
|
||||
{
|
||||
int retval;
|
||||
struct firmware_priv *fw_priv = kzalloc(sizeof(*fw_priv),
|
||||
GFP_KERNEL);
|
||||
struct device *f_dev = kzalloc(sizeof(*f_dev), GFP_KERNEL);
|
||||
|
||||
*dev_p = NULL;
|
||||
|
||||
if (!fw_priv || !f_dev) {
|
||||
printk(KERN_ERR "%s: kmalloc failed\n", __FUNCTION__);
|
||||
retval = -ENOMEM;
|
||||
goto error_kfree;
|
||||
}
|
||||
|
||||
init_completion(&fw_priv->completion);
|
||||
fw_priv->attr_data = firmware_attr_data_tmpl;
|
||||
strlcpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX);
|
||||
|
||||
fw_priv->timeout.function = firmware_class_timeout;
|
||||
fw_priv->timeout.data = (u_long) fw_priv;
|
||||
init_timer(&fw_priv->timeout);
|
||||
|
||||
fw_setup_device_id(f_dev, device);
|
||||
f_dev->parent = device;
|
||||
f_dev->class = &firmware_class;
|
||||
dev_set_drvdata(f_dev, fw_priv);
|
||||
retval = device_register(f_dev);
|
||||
if (retval) {
|
||||
printk(KERN_ERR "%s: device_register failed\n",
|
||||
__FUNCTION__);
|
||||
goto error_kfree;
|
||||
}
|
||||
*dev_p = f_dev;
|
||||
return 0;
|
||||
|
||||
error_kfree:
|
||||
kfree(fw_priv);
|
||||
kfree(f_dev);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int fw_setup_device(struct firmware *fw, struct device **dev_p,
|
||||
const char *fw_name, struct device *device,
|
||||
int uevent)
|
||||
{
|
||||
struct device *f_dev;
|
||||
struct firmware_priv *fw_priv;
|
||||
int retval;
|
||||
|
||||
*dev_p = NULL;
|
||||
retval = fw_register_device(&f_dev, fw_name, device);
|
||||
if (retval)
|
||||
goto out;
|
||||
|
||||
/* Need to pin this module until class device is destroyed */
|
||||
__module_get(THIS_MODULE);
|
||||
|
||||
fw_priv = dev_get_drvdata(f_dev);
|
||||
|
||||
fw_priv->fw = fw;
|
||||
retval = sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data);
|
||||
if (retval) {
|
||||
printk(KERN_ERR "%s: sysfs_create_bin_file failed\n",
|
||||
__FUNCTION__);
|
||||
goto error_unreg;
|
||||
}
|
||||
|
||||
retval = device_create_file(f_dev, &dev_attr_loading);
|
||||
if (retval) {
|
||||
printk(KERN_ERR "%s: device_create_file failed\n",
|
||||
__FUNCTION__);
|
||||
goto error_unreg;
|
||||
}
|
||||
|
||||
if (uevent)
|
||||
set_bit(FW_STATUS_READY, &fw_priv->status);
|
||||
else
|
||||
set_bit(FW_STATUS_READY_NOHOTPLUG, &fw_priv->status);
|
||||
*dev_p = f_dev;
|
||||
goto out;
|
||||
|
||||
error_unreg:
|
||||
device_unregister(f_dev);
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
_request_firmware(const struct firmware **firmware_p, const char *name,
|
||||
struct device *device, int uevent)
|
||||
{
|
||||
struct device *f_dev;
|
||||
struct firmware_priv *fw_priv;
|
||||
struct firmware *firmware;
|
||||
int retval;
|
||||
|
||||
if (!firmware_p)
|
||||
return -EINVAL;
|
||||
|
||||
*firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
|
||||
if (!firmware) {
|
||||
printk(KERN_ERR "%s: kmalloc(struct firmware) failed\n",
|
||||
__FUNCTION__);
|
||||
retval = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
retval = fw_setup_device(firmware, &f_dev, name, device, uevent);
|
||||
if (retval)
|
||||
goto error_kfree_fw;
|
||||
|
||||
fw_priv = dev_get_drvdata(f_dev);
|
||||
|
||||
if (uevent) {
|
||||
if (loading_timeout > 0) {
|
||||
fw_priv->timeout.expires = jiffies + loading_timeout * HZ;
|
||||
add_timer(&fw_priv->timeout);
|
||||
}
|
||||
|
||||
kobject_uevent(&f_dev->kobj, KOBJ_ADD);
|
||||
wait_for_completion(&fw_priv->completion);
|
||||
set_bit(FW_STATUS_DONE, &fw_priv->status);
|
||||
del_timer_sync(&fw_priv->timeout);
|
||||
} else
|
||||
wait_for_completion(&fw_priv->completion);
|
||||
|
||||
mutex_lock(&fw_lock);
|
||||
if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) {
|
||||
retval = -ENOENT;
|
||||
release_firmware(fw_priv->fw);
|
||||
*firmware_p = NULL;
|
||||
}
|
||||
fw_priv->fw = NULL;
|
||||
mutex_unlock(&fw_lock);
|
||||
device_unregister(f_dev);
|
||||
goto out;
|
||||
|
||||
error_kfree_fw:
|
||||
kfree(firmware);
|
||||
*firmware_p = NULL;
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* request_firmware: - send firmware request and wait for it
|
||||
* @firmware_p: pointer to firmware image
|
||||
* @name: name of firmware file
|
||||
* @device: device for which firmware is being loaded
|
||||
*
|
||||
* @firmware_p will be used to return a firmware image by the name
|
||||
* of @name for device @device.
|
||||
*
|
||||
* Should be called from user context where sleeping is allowed.
|
||||
*
|
||||
* @name will be used as $FIRMWARE in the uevent environment and
|
||||
* should be distinctive enough not to be confused with any other
|
||||
* firmware image for this or any other device.
|
||||
**/
|
||||
int
|
||||
request_firmware(const struct firmware **firmware_p, const char *name,
|
||||
struct device *device)
|
||||
{
|
||||
int uevent = 1;
|
||||
return _request_firmware(firmware_p, name, device, uevent);
|
||||
}
|
||||
|
||||
/**
|
||||
* release_firmware: - release the resource associated with a firmware image
|
||||
* @fw: firmware resource to release
|
||||
**/
|
||||
void
|
||||
release_firmware(const struct firmware *fw)
|
||||
{
|
||||
if (fw) {
|
||||
vfree(fw->data);
|
||||
kfree(fw);
|
||||
}
|
||||
}
|
||||
|
||||
/* Async support */
|
||||
struct firmware_work {
|
||||
struct work_struct work;
|
||||
struct module *module;
|
||||
const char *name;
|
||||
struct device *device;
|
||||
void *context;
|
||||
void (*cont)(const struct firmware *fw, void *context);
|
||||
int uevent;
|
||||
};
|
||||
|
||||
static int
|
||||
request_firmware_work_func(void *arg)
|
||||
{
|
||||
struct firmware_work *fw_work = arg;
|
||||
const struct firmware *fw;
|
||||
int ret;
|
||||
if (!arg) {
|
||||
WARN_ON(1);
|
||||
return 0;
|
||||
}
|
||||
ret = _request_firmware(&fw, fw_work->name, fw_work->device,
|
||||
fw_work->uevent);
|
||||
if (ret < 0)
|
||||
fw_work->cont(NULL, fw_work->context);
|
||||
else {
|
||||
fw_work->cont(fw, fw_work->context);
|
||||
release_firmware(fw);
|
||||
}
|
||||
module_put(fw_work->module);
|
||||
kfree(fw_work);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* request_firmware_nowait: asynchronous version of request_firmware
|
||||
* @module: module requesting the firmware
|
||||
* @uevent: sends uevent to copy the firmware image if this flag
|
||||
* is non-zero else the firmware copy must be done manually.
|
||||
* @name: name of firmware file
|
||||
* @device: device for which firmware is being loaded
|
||||
* @context: will be passed over to @cont, and
|
||||
* @fw may be %NULL if firmware request fails.
|
||||
* @cont: function will be called asynchronously when the firmware
|
||||
* request is over.
|
||||
*
|
||||
* Asynchronous variant of request_firmware() for contexts where
|
||||
* it is not possible to sleep.
|
||||
**/
|
||||
int
|
||||
request_firmware_nowait(
|
||||
struct module *module, int uevent,
|
||||
const char *name, struct device *device, void *context,
|
||||
void (*cont)(const struct firmware *fw, void *context))
|
||||
{
|
||||
struct task_struct *task;
|
||||
struct firmware_work *fw_work = kmalloc(sizeof (struct firmware_work),
|
||||
GFP_ATOMIC);
|
||||
|
||||
if (!fw_work)
|
||||
return -ENOMEM;
|
||||
if (!try_module_get(module)) {
|
||||
kfree(fw_work);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
*fw_work = (struct firmware_work) {
|
||||
.module = module,
|
||||
.name = name,
|
||||
.device = device,
|
||||
.context = context,
|
||||
.cont = cont,
|
||||
.uevent = uevent,
|
||||
};
|
||||
|
||||
task = kthread_run(request_firmware_work_func, fw_work,
|
||||
"firmware/%s", name);
|
||||
|
||||
if (IS_ERR(task)) {
|
||||
fw_work->cont(NULL, fw_work->context);
|
||||
module_put(fw_work->module);
|
||||
kfree(fw_work);
|
||||
return PTR_ERR(task);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init
|
||||
firmware_class_init(void)
|
||||
{
|
||||
int error;
|
||||
error = class_register(&firmware_class);
|
||||
if (error) {
|
||||
printk(KERN_ERR "%s: class_register failed\n", __FUNCTION__);
|
||||
return error;
|
||||
}
|
||||
error = class_create_file(&firmware_class, &class_attr_timeout);
|
||||
if (error) {
|
||||
printk(KERN_ERR "%s: class_create_file failed\n",
|
||||
__FUNCTION__);
|
||||
class_unregister(&firmware_class);
|
||||
}
|
||||
return error;
|
||||
|
||||
}
|
||||
static void __exit
|
||||
firmware_class_exit(void)
|
||||
{
|
||||
class_unregister(&firmware_class);
|
||||
}
|
||||
|
||||
fs_initcall(firmware_class_init);
|
||||
module_exit(firmware_class_exit);
|
||||
|
||||
EXPORT_SYMBOL(release_firmware);
|
||||
EXPORT_SYMBOL(request_firmware);
|
||||
EXPORT_SYMBOL(request_firmware_nowait);
|
||||
20
drivers/base/hypervisor.c
Normal file
20
drivers/base/hypervisor.c
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* hypervisor.c - /sys/hypervisor subsystem.
|
||||
*
|
||||
* Copyright (C) IBM Corp. 2006
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*/
|
||||
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/device.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
decl_subsys(hypervisor, NULL, NULL);
|
||||
EXPORT_SYMBOL_GPL(hypervisor_subsys);
|
||||
|
||||
int __init hypervisor_init(void)
|
||||
{
|
||||
return subsystem_register(&hypervisor_subsys);
|
||||
}
|
||||
40
drivers/base/init.c
Normal file
40
drivers/base/init.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* Copyright (c) 2002-3 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/memory.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
/**
|
||||
* driver_init - initialize driver model.
|
||||
*
|
||||
* Call the driver model init functions to initialize their
|
||||
* subsystems. Called early from init/main.c.
|
||||
*/
|
||||
|
||||
void __init driver_init(void)
|
||||
{
|
||||
/* These are the core pieces */
|
||||
devices_init();
|
||||
buses_init();
|
||||
classes_init();
|
||||
firmware_init();
|
||||
hypervisor_init();
|
||||
|
||||
/* These are also core pieces, but must come after the
|
||||
* core core pieces.
|
||||
*/
|
||||
platform_bus_init();
|
||||
system_bus_init();
|
||||
cpu_dev_init();
|
||||
memory_dev_init();
|
||||
attribute_container_init();
|
||||
}
|
||||
180
drivers/base/isa.c
Normal file
180
drivers/base/isa.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* ISA bus.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/isa.h>
|
||||
|
||||
static struct device isa_bus = {
|
||||
.bus_id = "isa"
|
||||
};
|
||||
|
||||
struct isa_dev {
|
||||
struct device dev;
|
||||
struct device *next;
|
||||
unsigned int id;
|
||||
};
|
||||
|
||||
#define to_isa_dev(x) container_of((x), struct isa_dev, dev)
|
||||
|
||||
static int isa_bus_match(struct device *dev, struct device_driver *driver)
|
||||
{
|
||||
struct isa_driver *isa_driver = to_isa_driver(driver);
|
||||
|
||||
if (dev->platform_data == isa_driver) {
|
||||
if (!isa_driver->match ||
|
||||
isa_driver->match(dev, to_isa_dev(dev)->id))
|
||||
return 1;
|
||||
dev->platform_data = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isa_bus_probe(struct device *dev)
|
||||
{
|
||||
struct isa_driver *isa_driver = dev->platform_data;
|
||||
|
||||
if (isa_driver->probe)
|
||||
return isa_driver->probe(dev, to_isa_dev(dev)->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isa_bus_remove(struct device *dev)
|
||||
{
|
||||
struct isa_driver *isa_driver = dev->platform_data;
|
||||
|
||||
if (isa_driver->remove)
|
||||
return isa_driver->remove(dev, to_isa_dev(dev)->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void isa_bus_shutdown(struct device *dev)
|
||||
{
|
||||
struct isa_driver *isa_driver = dev->platform_data;
|
||||
|
||||
if (isa_driver->shutdown)
|
||||
isa_driver->shutdown(dev, to_isa_dev(dev)->id);
|
||||
}
|
||||
|
||||
static int isa_bus_suspend(struct device *dev, pm_message_t state)
|
||||
{
|
||||
struct isa_driver *isa_driver = dev->platform_data;
|
||||
|
||||
if (isa_driver->suspend)
|
||||
return isa_driver->suspend(dev, to_isa_dev(dev)->id, state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isa_bus_resume(struct device *dev)
|
||||
{
|
||||
struct isa_driver *isa_driver = dev->platform_data;
|
||||
|
||||
if (isa_driver->resume)
|
||||
return isa_driver->resume(dev, to_isa_dev(dev)->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bus_type isa_bus_type = {
|
||||
.name = "isa",
|
||||
.match = isa_bus_match,
|
||||
.probe = isa_bus_probe,
|
||||
.remove = isa_bus_remove,
|
||||
.shutdown = isa_bus_shutdown,
|
||||
.suspend = isa_bus_suspend,
|
||||
.resume = isa_bus_resume
|
||||
};
|
||||
|
||||
static void isa_dev_release(struct device *dev)
|
||||
{
|
||||
kfree(to_isa_dev(dev));
|
||||
}
|
||||
|
||||
void isa_unregister_driver(struct isa_driver *isa_driver)
|
||||
{
|
||||
struct device *dev = isa_driver->devices;
|
||||
|
||||
while (dev) {
|
||||
struct device *tmp = to_isa_dev(dev)->next;
|
||||
device_unregister(dev);
|
||||
dev = tmp;
|
||||
}
|
||||
driver_unregister(&isa_driver->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isa_unregister_driver);
|
||||
|
||||
int isa_register_driver(struct isa_driver *isa_driver, unsigned int ndev)
|
||||
{
|
||||
int error;
|
||||
unsigned int id;
|
||||
|
||||
isa_driver->driver.bus = &isa_bus_type;
|
||||
isa_driver->devices = NULL;
|
||||
|
||||
error = driver_register(&isa_driver->driver);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
for (id = 0; id < ndev; id++) {
|
||||
struct isa_dev *isa_dev;
|
||||
|
||||
isa_dev = kzalloc(sizeof *isa_dev, GFP_KERNEL);
|
||||
if (!isa_dev) {
|
||||
error = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
isa_dev->dev.parent = &isa_bus;
|
||||
isa_dev->dev.bus = &isa_bus_type;
|
||||
|
||||
snprintf(isa_dev->dev.bus_id, BUS_ID_SIZE, "%s.%u",
|
||||
isa_driver->driver.name, id);
|
||||
|
||||
isa_dev->dev.platform_data = isa_driver;
|
||||
isa_dev->dev.release = isa_dev_release;
|
||||
isa_dev->id = id;
|
||||
|
||||
error = device_register(&isa_dev->dev);
|
||||
if (error) {
|
||||
put_device(&isa_dev->dev);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isa_dev->dev.platform_data) {
|
||||
isa_dev->next = isa_driver->devices;
|
||||
isa_driver->devices = &isa_dev->dev;
|
||||
} else
|
||||
device_unregister(&isa_dev->dev);
|
||||
}
|
||||
|
||||
if (!error && !isa_driver->devices)
|
||||
error = -ENODEV;
|
||||
|
||||
if (error)
|
||||
isa_unregister_driver(isa_driver);
|
||||
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isa_register_driver);
|
||||
|
||||
static int __init isa_bus_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = bus_register(&isa_bus_type);
|
||||
if (!error) {
|
||||
error = device_register(&isa_bus);
|
||||
if (error)
|
||||
bus_unregister(&isa_bus_type);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
device_initcall(isa_bus_init);
|
||||
155
drivers/base/map.c
Normal file
155
drivers/base/map.c
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* linux/drivers/base/map.c
|
||||
*
|
||||
* (C) Copyright Al Viro 2002,2003
|
||||
* Released under GPL v2.
|
||||
*
|
||||
* NOTE: data structure needs to be changed. It works, but for large dev_t
|
||||
* it will be too slow. It is isolated, though, so these changes will be
|
||||
* local to that file.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/kobj_map.h>
|
||||
|
||||
struct kobj_map {
|
||||
struct probe {
|
||||
struct probe *next;
|
||||
dev_t dev;
|
||||
unsigned long range;
|
||||
struct module *owner;
|
||||
kobj_probe_t *get;
|
||||
int (*lock)(dev_t, void *);
|
||||
void *data;
|
||||
} *probes[255];
|
||||
struct mutex *lock;
|
||||
};
|
||||
|
||||
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
|
||||
struct module *module, kobj_probe_t *probe,
|
||||
int (*lock)(dev_t, void *), void *data)
|
||||
{
|
||||
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
|
||||
unsigned index = MAJOR(dev);
|
||||
unsigned i;
|
||||
struct probe *p;
|
||||
|
||||
if (n > 255)
|
||||
n = 255;
|
||||
|
||||
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
|
||||
|
||||
if (p == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < n; i++, p++) {
|
||||
p->owner = module;
|
||||
p->get = probe;
|
||||
p->lock = lock;
|
||||
p->dev = dev;
|
||||
p->range = range;
|
||||
p->data = data;
|
||||
}
|
||||
mutex_lock(domain->lock);
|
||||
for (i = 0, p -= n; i < n; i++, p++, index++) {
|
||||
struct probe **s = &domain->probes[index % 255];
|
||||
while (*s && (*s)->range < range)
|
||||
s = &(*s)->next;
|
||||
p->next = *s;
|
||||
*s = p;
|
||||
}
|
||||
mutex_unlock(domain->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range)
|
||||
{
|
||||
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
|
||||
unsigned index = MAJOR(dev);
|
||||
unsigned i;
|
||||
struct probe *found = NULL;
|
||||
|
||||
if (n > 255)
|
||||
n = 255;
|
||||
|
||||
mutex_lock(domain->lock);
|
||||
for (i = 0; i < n; i++, index++) {
|
||||
struct probe **s;
|
||||
for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) {
|
||||
struct probe *p = *s;
|
||||
if (p->dev == dev && p->range == range) {
|
||||
*s = p->next;
|
||||
if (!found)
|
||||
found = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(domain->lock);
|
||||
kfree(found);
|
||||
}
|
||||
|
||||
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
|
||||
{
|
||||
struct kobject *kobj;
|
||||
struct probe *p;
|
||||
unsigned long best = ~0UL;
|
||||
|
||||
retry:
|
||||
mutex_lock(domain->lock);
|
||||
for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {
|
||||
struct kobject *(*probe)(dev_t, int *, void *);
|
||||
struct module *owner;
|
||||
void *data;
|
||||
|
||||
if (p->dev > dev || p->dev + p->range - 1 < dev)
|
||||
continue;
|
||||
if (p->range - 1 >= best)
|
||||
break;
|
||||
if (!try_module_get(p->owner))
|
||||
continue;
|
||||
owner = p->owner;
|
||||
data = p->data;
|
||||
probe = p->get;
|
||||
best = p->range - 1;
|
||||
*index = dev - p->dev;
|
||||
if (p->lock && p->lock(dev, data) < 0) {
|
||||
module_put(owner);
|
||||
continue;
|
||||
}
|
||||
mutex_unlock(domain->lock);
|
||||
kobj = probe(dev, index, data);
|
||||
/* Currently ->owner protects _only_ ->probe() itself. */
|
||||
module_put(owner);
|
||||
if (kobj)
|
||||
return kobj;
|
||||
goto retry;
|
||||
}
|
||||
mutex_unlock(domain->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)
|
||||
{
|
||||
struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);
|
||||
struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);
|
||||
int i;
|
||||
|
||||
if ((p == NULL) || (base == NULL)) {
|
||||
kfree(p);
|
||||
kfree(base);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
base->dev = 1;
|
||||
base->range = ~0;
|
||||
base->get = base_probe;
|
||||
for (i = 0; i < 255; i++)
|
||||
p->probes[i] = base;
|
||||
p->lock = lock;
|
||||
return p;
|
||||
}
|
||||
464
drivers/base/memory.c
Normal file
464
drivers/base/memory.c
Normal file
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* drivers/base/memory.c - basic Memory class support
|
||||
*
|
||||
* Written by Matt Tolentino <matthew.e.tolentino@intel.com>
|
||||
* Dave Hansen <haveblue@us.ibm.com>
|
||||
*
|
||||
* This file provides the necessary infrastructure to represent
|
||||
* a SPARSEMEM-memory-model system's physical memory in /sysfs.
|
||||
* All arch-independent code that assumes MEMORY_HOTPLUG requires
|
||||
* SPARSEMEM should be contained here, or in mm/memory_hotplug.c.
|
||||
*/
|
||||
|
||||
#include <linux/sysdev.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/topology.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/memory.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/memory_hotplug.h>
|
||||
#include <linux/mm.h>
|
||||
#include <asm/atomic.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#define MEMORY_CLASS_NAME "memory"
|
||||
|
||||
static struct sysdev_class memory_sysdev_class = {
|
||||
set_kset_name(MEMORY_CLASS_NAME),
|
||||
};
|
||||
|
||||
static const char *memory_uevent_name(struct kset *kset, struct kobject *kobj)
|
||||
{
|
||||
return MEMORY_CLASS_NAME;
|
||||
}
|
||||
|
||||
static int memory_uevent(struct kset *kset, struct kobject *kobj, char **envp,
|
||||
int num_envp, char *buffer, int buffer_size)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct kset_uevent_ops memory_uevent_ops = {
|
||||
.name = memory_uevent_name,
|
||||
.uevent = memory_uevent,
|
||||
};
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(memory_chain);
|
||||
|
||||
int register_memory_notifier(struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_register(&memory_chain, nb);
|
||||
}
|
||||
|
||||
void unregister_memory_notifier(struct notifier_block *nb)
|
||||
{
|
||||
blocking_notifier_chain_unregister(&memory_chain, nb);
|
||||
}
|
||||
|
||||
/*
|
||||
* register_memory - Setup a sysfs device for a memory block
|
||||
*/
|
||||
int register_memory(struct memory_block *memory, struct mem_section *section,
|
||||
struct node *root)
|
||||
{
|
||||
int error;
|
||||
|
||||
memory->sysdev.cls = &memory_sysdev_class;
|
||||
memory->sysdev.id = __section_nr(section);
|
||||
|
||||
error = sysdev_register(&memory->sysdev);
|
||||
|
||||
if (root && !error)
|
||||
error = sysfs_create_link(&root->sysdev.kobj,
|
||||
&memory->sysdev.kobj,
|
||||
kobject_name(&memory->sysdev.kobj));
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_memory(struct memory_block *memory, struct mem_section *section,
|
||||
struct node *root)
|
||||
{
|
||||
BUG_ON(memory->sysdev.cls != &memory_sysdev_class);
|
||||
BUG_ON(memory->sysdev.id != __section_nr(section));
|
||||
|
||||
sysdev_unregister(&memory->sysdev);
|
||||
if (root)
|
||||
sysfs_remove_link(&root->sysdev.kobj,
|
||||
kobject_name(&memory->sysdev.kobj));
|
||||
}
|
||||
|
||||
/*
|
||||
* use this as the physical section index that this memsection
|
||||
* uses.
|
||||
*/
|
||||
|
||||
static ssize_t show_mem_phys_index(struct sys_device *dev, char *buf)
|
||||
{
|
||||
struct memory_block *mem =
|
||||
container_of(dev, struct memory_block, sysdev);
|
||||
return sprintf(buf, "%08lx\n", mem->phys_index);
|
||||
}
|
||||
|
||||
/*
|
||||
* online, offline, going offline, etc.
|
||||
*/
|
||||
static ssize_t show_mem_state(struct sys_device *dev, char *buf)
|
||||
{
|
||||
struct memory_block *mem =
|
||||
container_of(dev, struct memory_block, sysdev);
|
||||
ssize_t len = 0;
|
||||
|
||||
/*
|
||||
* We can probably put these states in a nice little array
|
||||
* so that they're not open-coded
|
||||
*/
|
||||
switch (mem->state) {
|
||||
case MEM_ONLINE:
|
||||
len = sprintf(buf, "online\n");
|
||||
break;
|
||||
case MEM_OFFLINE:
|
||||
len = sprintf(buf, "offline\n");
|
||||
break;
|
||||
case MEM_GOING_OFFLINE:
|
||||
len = sprintf(buf, "going-offline\n");
|
||||
break;
|
||||
default:
|
||||
len = sprintf(buf, "ERROR-UNKNOWN-%ld\n",
|
||||
mem->state);
|
||||
WARN_ON(1);
|
||||
break;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static inline int memory_notify(unsigned long val, void *v)
|
||||
{
|
||||
return blocking_notifier_call_chain(&memory_chain, val, v);
|
||||
}
|
||||
|
||||
/*
|
||||
* MEMORY_HOTPLUG depends on SPARSEMEM in mm/Kconfig, so it is
|
||||
* OK to have direct references to sparsemem variables in here.
|
||||
*/
|
||||
static int
|
||||
memory_block_action(struct memory_block *mem, unsigned long action)
|
||||
{
|
||||
int i;
|
||||
unsigned long psection;
|
||||
unsigned long start_pfn, start_paddr;
|
||||
struct page *first_page;
|
||||
int ret;
|
||||
int old_state = mem->state;
|
||||
|
||||
psection = mem->phys_index;
|
||||
first_page = pfn_to_page(psection << PFN_SECTION_SHIFT);
|
||||
|
||||
/*
|
||||
* The probe routines leave the pages reserved, just
|
||||
* as the bootmem code does. Make sure they're still
|
||||
* that way.
|
||||
*/
|
||||
if (action == MEM_ONLINE) {
|
||||
for (i = 0; i < PAGES_PER_SECTION; i++) {
|
||||
if (PageReserved(first_page+i))
|
||||
continue;
|
||||
|
||||
printk(KERN_WARNING "section number %ld page number %d "
|
||||
"not reserved, was it already online? \n",
|
||||
psection, i);
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MEM_ONLINE:
|
||||
start_pfn = page_to_pfn(first_page);
|
||||
ret = online_pages(start_pfn, PAGES_PER_SECTION);
|
||||
break;
|
||||
case MEM_OFFLINE:
|
||||
mem->state = MEM_GOING_OFFLINE;
|
||||
memory_notify(MEM_GOING_OFFLINE, NULL);
|
||||
start_paddr = page_to_pfn(first_page) << PAGE_SHIFT;
|
||||
ret = remove_memory(start_paddr,
|
||||
PAGES_PER_SECTION << PAGE_SHIFT);
|
||||
if (ret) {
|
||||
mem->state = old_state;
|
||||
break;
|
||||
}
|
||||
memory_notify(MEM_MAPPING_INVALID, NULL);
|
||||
break;
|
||||
default:
|
||||
printk(KERN_WARNING "%s(%p, %ld) unknown action: %ld\n",
|
||||
__FUNCTION__, mem, action, action);
|
||||
WARN_ON(1);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
/*
|
||||
* For now, only notify on successful memory operations
|
||||
*/
|
||||
if (!ret)
|
||||
memory_notify(action, NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int memory_block_change_state(struct memory_block *mem,
|
||||
unsigned long to_state, unsigned long from_state_req)
|
||||
{
|
||||
int ret = 0;
|
||||
down(&mem->state_sem);
|
||||
|
||||
if (mem->state != from_state_req) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = memory_block_action(mem, to_state);
|
||||
if (!ret)
|
||||
mem->state = to_state;
|
||||
|
||||
out:
|
||||
up(&mem->state_sem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
store_mem_state(struct sys_device *dev, const char *buf, size_t count)
|
||||
{
|
||||
struct memory_block *mem;
|
||||
unsigned int phys_section_nr;
|
||||
int ret = -EINVAL;
|
||||
|
||||
mem = container_of(dev, struct memory_block, sysdev);
|
||||
phys_section_nr = mem->phys_index;
|
||||
|
||||
if (!valid_section_nr(phys_section_nr))
|
||||
goto out;
|
||||
|
||||
if (!strncmp(buf, "online", min((int)count, 6)))
|
||||
ret = memory_block_change_state(mem, MEM_ONLINE, MEM_OFFLINE);
|
||||
else if(!strncmp(buf, "offline", min((int)count, 7)))
|
||||
ret = memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE);
|
||||
out:
|
||||
if (ret)
|
||||
return ret;
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* phys_device is a bad name for this. What I really want
|
||||
* is a way to differentiate between memory ranges that
|
||||
* are part of physical devices that constitute
|
||||
* a complete removable unit or fru.
|
||||
* i.e. do these ranges belong to the same physical device,
|
||||
* s.t. if I offline all of these sections I can then
|
||||
* remove the physical device?
|
||||
*/
|
||||
static ssize_t show_phys_device(struct sys_device *dev, char *buf)
|
||||
{
|
||||
struct memory_block *mem =
|
||||
container_of(dev, struct memory_block, sysdev);
|
||||
return sprintf(buf, "%d\n", mem->phys_device);
|
||||
}
|
||||
|
||||
static SYSDEV_ATTR(phys_index, 0444, show_mem_phys_index, NULL);
|
||||
static SYSDEV_ATTR(state, 0644, show_mem_state, store_mem_state);
|
||||
static SYSDEV_ATTR(phys_device, 0444, show_phys_device, NULL);
|
||||
|
||||
#define mem_create_simple_file(mem, attr_name) \
|
||||
sysdev_create_file(&mem->sysdev, &attr_##attr_name)
|
||||
#define mem_remove_simple_file(mem, attr_name) \
|
||||
sysdev_remove_file(&mem->sysdev, &attr_##attr_name)
|
||||
|
||||
/*
|
||||
* Block size attribute stuff
|
||||
*/
|
||||
static ssize_t
|
||||
print_block_size(struct class *class, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%lx\n", (unsigned long)PAGES_PER_SECTION * PAGE_SIZE);
|
||||
}
|
||||
|
||||
static CLASS_ATTR(block_size_bytes, 0444, print_block_size, NULL);
|
||||
|
||||
static int block_size_init(void)
|
||||
{
|
||||
return sysfs_create_file(&memory_sysdev_class.kset.kobj,
|
||||
&class_attr_block_size_bytes.attr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some architectures will have custom drivers to do this, and
|
||||
* will not need to do it from userspace. The fake hot-add code
|
||||
* as well as ppc64 will do all of their discovery in userspace
|
||||
* and will require this interface.
|
||||
*/
|
||||
#ifdef CONFIG_ARCH_MEMORY_PROBE
|
||||
static ssize_t
|
||||
memory_probe_store(struct class *class, const char *buf, size_t count)
|
||||
{
|
||||
u64 phys_addr;
|
||||
int nid;
|
||||
int ret;
|
||||
|
||||
phys_addr = simple_strtoull(buf, NULL, 0);
|
||||
|
||||
nid = memory_add_physaddr_to_nid(phys_addr);
|
||||
ret = add_memory(nid, phys_addr, PAGES_PER_SECTION << PAGE_SHIFT);
|
||||
|
||||
if (ret)
|
||||
count = ret;
|
||||
|
||||
return count;
|
||||
}
|
||||
static CLASS_ATTR(probe, 0700, NULL, memory_probe_store);
|
||||
|
||||
static int memory_probe_init(void)
|
||||
{
|
||||
return sysfs_create_file(&memory_sysdev_class.kset.kobj,
|
||||
&class_attr_probe.attr);
|
||||
}
|
||||
#else
|
||||
static inline int memory_probe_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Note that phys_device is optional. It is here to allow for
|
||||
* differentiation between which *physical* devices each
|
||||
* section belongs to...
|
||||
*/
|
||||
|
||||
static int add_memory_block(unsigned long node_id, struct mem_section *section,
|
||||
unsigned long state, int phys_device)
|
||||
{
|
||||
struct memory_block *mem = kzalloc(sizeof(*mem), GFP_KERNEL);
|
||||
int ret = 0;
|
||||
|
||||
if (!mem)
|
||||
return -ENOMEM;
|
||||
|
||||
mem->phys_index = __section_nr(section);
|
||||
mem->state = state;
|
||||
init_MUTEX(&mem->state_sem);
|
||||
mem->phys_device = phys_device;
|
||||
|
||||
ret = register_memory(mem, section, NULL);
|
||||
if (!ret)
|
||||
ret = mem_create_simple_file(mem, phys_index);
|
||||
if (!ret)
|
||||
ret = mem_create_simple_file(mem, state);
|
||||
if (!ret)
|
||||
ret = mem_create_simple_file(mem, phys_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* For now, we have a linear search to go find the appropriate
|
||||
* memory_block corresponding to a particular phys_index. If
|
||||
* this gets to be a real problem, we can always use a radix
|
||||
* tree or something here.
|
||||
*
|
||||
* This could be made generic for all sysdev classes.
|
||||
*/
|
||||
static struct memory_block *find_memory_block(struct mem_section *section)
|
||||
{
|
||||
struct kobject *kobj;
|
||||
struct sys_device *sysdev;
|
||||
struct memory_block *mem;
|
||||
char name[sizeof(MEMORY_CLASS_NAME) + 9 + 1];
|
||||
|
||||
/*
|
||||
* This only works because we know that section == sysdev->id
|
||||
* slightly redundant with sysdev_register()
|
||||
*/
|
||||
sprintf(&name[0], "%s%d", MEMORY_CLASS_NAME, __section_nr(section));
|
||||
|
||||
kobj = kset_find_obj(&memory_sysdev_class.kset, name);
|
||||
if (!kobj)
|
||||
return NULL;
|
||||
|
||||
sysdev = container_of(kobj, struct sys_device, kobj);
|
||||
mem = container_of(sysdev, struct memory_block, sysdev);
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
int remove_memory_block(unsigned long node_id, struct mem_section *section,
|
||||
int phys_device)
|
||||
{
|
||||
struct memory_block *mem;
|
||||
|
||||
mem = find_memory_block(section);
|
||||
mem_remove_simple_file(mem, phys_index);
|
||||
mem_remove_simple_file(mem, state);
|
||||
mem_remove_simple_file(mem, phys_device);
|
||||
unregister_memory(mem, section, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* need an interface for the VM to add new memory regions,
|
||||
* but without onlining it.
|
||||
*/
|
||||
int register_new_memory(struct mem_section *section)
|
||||
{
|
||||
return add_memory_block(0, section, MEM_OFFLINE, 0);
|
||||
}
|
||||
|
||||
int unregister_memory_section(struct mem_section *section)
|
||||
{
|
||||
if (!valid_section(section))
|
||||
return -EINVAL;
|
||||
|
||||
return remove_memory_block(0, section, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the sysfs support for memory devices...
|
||||
*/
|
||||
int __init memory_dev_init(void)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret;
|
||||
int err;
|
||||
|
||||
memory_sysdev_class.kset.uevent_ops = &memory_uevent_ops;
|
||||
ret = sysdev_class_register(&memory_sysdev_class);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Create entries for memory sections that were found
|
||||
* during boot and have been initialized
|
||||
*/
|
||||
for (i = 0; i < NR_MEM_SECTIONS; i++) {
|
||||
if (!valid_section_nr(i))
|
||||
continue;
|
||||
err = add_memory_block(0, __nr_to_section(i), MEM_ONLINE, 0);
|
||||
if (!ret)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
err = memory_probe_init();
|
||||
if (!ret)
|
||||
ret = err;
|
||||
err = block_size_init();
|
||||
if (!ret)
|
||||
ret = err;
|
||||
out:
|
||||
if (ret)
|
||||
printk(KERN_ERR "%s() failed: %d\n", __FUNCTION__, ret);
|
||||
return ret;
|
||||
}
|
||||
239
drivers/base/node.c
Normal file
239
drivers/base/node.c
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* drivers/base/node.c - basic Node class support
|
||||
*/
|
||||
|
||||
#include <linux/sysdev.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/node.h>
|
||||
#include <linux/hugetlb.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/topology.h>
|
||||
#include <linux/nodemask.h>
|
||||
#include <linux/cpu.h>
|
||||
|
||||
static struct sysdev_class node_class = {
|
||||
set_kset_name("node"),
|
||||
};
|
||||
|
||||
|
||||
static ssize_t node_read_cpumap(struct sys_device * dev, char * buf)
|
||||
{
|
||||
struct node *node_dev = to_node(dev);
|
||||
cpumask_t mask = node_to_cpumask(node_dev->sysdev.id);
|
||||
int len;
|
||||
|
||||
/* 2004/06/03: buf currently PAGE_SIZE, need > 1 char per 4 bits. */
|
||||
BUILD_BUG_ON(MAX_NUMNODES/4 > PAGE_SIZE/2);
|
||||
|
||||
len = cpumask_scnprintf(buf, PAGE_SIZE-1, mask);
|
||||
len += sprintf(buf + len, "\n");
|
||||
return len;
|
||||
}
|
||||
|
||||
static SYSDEV_ATTR(cpumap, S_IRUGO, node_read_cpumap, NULL);
|
||||
|
||||
#define K(x) ((x) << (PAGE_SHIFT - 10))
|
||||
static ssize_t node_read_meminfo(struct sys_device * dev, char * buf)
|
||||
{
|
||||
int n;
|
||||
int nid = dev->id;
|
||||
struct sysinfo i;
|
||||
|
||||
si_meminfo_node(&i, nid);
|
||||
|
||||
n = sprintf(buf, "\n"
|
||||
"Node %d MemTotal: %8lu kB\n"
|
||||
"Node %d MemFree: %8lu kB\n"
|
||||
"Node %d MemUsed: %8lu kB\n"
|
||||
"Node %d Active: %8lu kB\n"
|
||||
"Node %d Inactive: %8lu kB\n"
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
"Node %d HighTotal: %8lu kB\n"
|
||||
"Node %d HighFree: %8lu kB\n"
|
||||
"Node %d LowTotal: %8lu kB\n"
|
||||
"Node %d LowFree: %8lu kB\n"
|
||||
#endif
|
||||
"Node %d Dirty: %8lu kB\n"
|
||||
"Node %d Writeback: %8lu kB\n"
|
||||
"Node %d FilePages: %8lu kB\n"
|
||||
"Node %d Mapped: %8lu kB\n"
|
||||
"Node %d AnonPages: %8lu kB\n"
|
||||
"Node %d PageTables: %8lu kB\n"
|
||||
"Node %d NFS_Unstable: %8lu kB\n"
|
||||
"Node %d Bounce: %8lu kB\n"
|
||||
"Node %d Slab: %8lu kB\n"
|
||||
"Node %d SReclaimable: %8lu kB\n"
|
||||
"Node %d SUnreclaim: %8lu kB\n",
|
||||
nid, K(i.totalram),
|
||||
nid, K(i.freeram),
|
||||
nid, K(i.totalram - i.freeram),
|
||||
nid, node_page_state(nid, NR_ACTIVE),
|
||||
nid, node_page_state(nid, NR_INACTIVE),
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
nid, K(i.totalhigh),
|
||||
nid, K(i.freehigh),
|
||||
nid, K(i.totalram - i.totalhigh),
|
||||
nid, K(i.freeram - i.freehigh),
|
||||
#endif
|
||||
nid, K(node_page_state(nid, NR_FILE_DIRTY)),
|
||||
nid, K(node_page_state(nid, NR_WRITEBACK)),
|
||||
nid, K(node_page_state(nid, NR_FILE_PAGES)),
|
||||
nid, K(node_page_state(nid, NR_FILE_MAPPED)),
|
||||
nid, K(node_page_state(nid, NR_ANON_PAGES)),
|
||||
nid, K(node_page_state(nid, NR_PAGETABLE)),
|
||||
nid, K(node_page_state(nid, NR_UNSTABLE_NFS)),
|
||||
nid, K(node_page_state(nid, NR_BOUNCE)),
|
||||
nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE) +
|
||||
node_page_state(nid, NR_SLAB_UNRECLAIMABLE)),
|
||||
nid, K(node_page_state(nid, NR_SLAB_RECLAIMABLE)),
|
||||
nid, K(node_page_state(nid, NR_SLAB_UNRECLAIMABLE)));
|
||||
n += hugetlb_report_node_meminfo(nid, buf + n);
|
||||
return n;
|
||||
}
|
||||
|
||||
#undef K
|
||||
static SYSDEV_ATTR(meminfo, S_IRUGO, node_read_meminfo, NULL);
|
||||
|
||||
static ssize_t node_read_numastat(struct sys_device * dev, char * buf)
|
||||
{
|
||||
return sprintf(buf,
|
||||
"numa_hit %lu\n"
|
||||
"numa_miss %lu\n"
|
||||
"numa_foreign %lu\n"
|
||||
"interleave_hit %lu\n"
|
||||
"local_node %lu\n"
|
||||
"other_node %lu\n",
|
||||
node_page_state(dev->id, NUMA_HIT),
|
||||
node_page_state(dev->id, NUMA_MISS),
|
||||
node_page_state(dev->id, NUMA_FOREIGN),
|
||||
node_page_state(dev->id, NUMA_INTERLEAVE_HIT),
|
||||
node_page_state(dev->id, NUMA_LOCAL),
|
||||
node_page_state(dev->id, NUMA_OTHER));
|
||||
}
|
||||
static SYSDEV_ATTR(numastat, S_IRUGO, node_read_numastat, NULL);
|
||||
|
||||
static ssize_t node_read_distance(struct sys_device * dev, char * buf)
|
||||
{
|
||||
int nid = dev->id;
|
||||
int len = 0;
|
||||
int i;
|
||||
|
||||
/* buf currently PAGE_SIZE, need ~4 chars per node */
|
||||
BUILD_BUG_ON(MAX_NUMNODES*4 > PAGE_SIZE/2);
|
||||
|
||||
for_each_online_node(i)
|
||||
len += sprintf(buf + len, "%s%d", i ? " " : "", node_distance(nid, i));
|
||||
|
||||
len += sprintf(buf + len, "\n");
|
||||
return len;
|
||||
}
|
||||
static SYSDEV_ATTR(distance, S_IRUGO, node_read_distance, NULL);
|
||||
|
||||
|
||||
/*
|
||||
* register_node - Setup a sysfs device for a node.
|
||||
* @num - Node number to use when creating the device.
|
||||
*
|
||||
* Initialize and register the node device.
|
||||
*/
|
||||
int register_node(struct node *node, int num, struct node *parent)
|
||||
{
|
||||
int error;
|
||||
|
||||
node->sysdev.id = num;
|
||||
node->sysdev.cls = &node_class;
|
||||
error = sysdev_register(&node->sysdev);
|
||||
|
||||
if (!error){
|
||||
sysdev_create_file(&node->sysdev, &attr_cpumap);
|
||||
sysdev_create_file(&node->sysdev, &attr_meminfo);
|
||||
sysdev_create_file(&node->sysdev, &attr_numastat);
|
||||
sysdev_create_file(&node->sysdev, &attr_distance);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister_node - unregister a node device
|
||||
* @node: node going away
|
||||
*
|
||||
* Unregisters a node device @node. All the devices on the node must be
|
||||
* unregistered before calling this function.
|
||||
*/
|
||||
void unregister_node(struct node *node)
|
||||
{
|
||||
sysdev_remove_file(&node->sysdev, &attr_cpumap);
|
||||
sysdev_remove_file(&node->sysdev, &attr_meminfo);
|
||||
sysdev_remove_file(&node->sysdev, &attr_numastat);
|
||||
sysdev_remove_file(&node->sysdev, &attr_distance);
|
||||
|
||||
sysdev_unregister(&node->sysdev);
|
||||
}
|
||||
|
||||
struct node node_devices[MAX_NUMNODES];
|
||||
|
||||
/*
|
||||
* register cpu under node
|
||||
*/
|
||||
int register_cpu_under_node(unsigned int cpu, unsigned int nid)
|
||||
{
|
||||
if (node_online(nid)) {
|
||||
struct sys_device *obj = get_cpu_sysdev(cpu);
|
||||
if (!obj)
|
||||
return 0;
|
||||
return sysfs_create_link(&node_devices[nid].sysdev.kobj,
|
||||
&obj->kobj,
|
||||
kobject_name(&obj->kobj));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int unregister_cpu_under_node(unsigned int cpu, unsigned int nid)
|
||||
{
|
||||
if (node_online(nid)) {
|
||||
struct sys_device *obj = get_cpu_sysdev(cpu);
|
||||
if (obj)
|
||||
sysfs_remove_link(&node_devices[nid].sysdev.kobj,
|
||||
kobject_name(&obj->kobj));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int register_one_node(int nid)
|
||||
{
|
||||
int error = 0;
|
||||
int cpu;
|
||||
|
||||
if (node_online(nid)) {
|
||||
int p_node = parent_node(nid);
|
||||
struct node *parent = NULL;
|
||||
|
||||
if (p_node != nid)
|
||||
parent = &node_devices[p_node];
|
||||
|
||||
error = register_node(&node_devices[nid], nid, parent);
|
||||
|
||||
/* link cpu under this node */
|
||||
for_each_present_cpu(cpu) {
|
||||
if (cpu_to_node(cpu) == nid)
|
||||
register_cpu_under_node(cpu, nid);
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
|
||||
}
|
||||
|
||||
void unregister_one_node(int nid)
|
||||
{
|
||||
unregister_node(&node_devices[nid]);
|
||||
}
|
||||
|
||||
static int __init register_node_type(void)
|
||||
{
|
||||
return sysdev_class_register(&node_class);
|
||||
}
|
||||
postcore_initcall(register_node_type);
|
||||
645
drivers/base/platform.c
Normal file
645
drivers/base/platform.c
Normal file
@@ -0,0 +1,645 @@
|
||||
/*
|
||||
* platform.c - platform 'pseudo' bus for legacy devices
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* Copyright (c) 2002-3 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
* Please see Documentation/driver-model/platform.txt for more
|
||||
* information.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define to_platform_driver(drv) (container_of((drv), struct platform_driver, driver))
|
||||
|
||||
struct device platform_bus = {
|
||||
.bus_id = "platform",
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(platform_bus);
|
||||
|
||||
/**
|
||||
* platform_get_resource - get a resource for a device
|
||||
* @dev: platform device
|
||||
* @type: resource type
|
||||
* @num: resource index
|
||||
*/
|
||||
struct resource *
|
||||
platform_get_resource(struct platform_device *dev, unsigned int type,
|
||||
unsigned int num)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < dev->num_resources; i++) {
|
||||
struct resource *r = &dev->resource[i];
|
||||
|
||||
if ((r->flags & (IORESOURCE_IO|IORESOURCE_MEM|
|
||||
IORESOURCE_IRQ|IORESOURCE_DMA))
|
||||
== type)
|
||||
if (num-- == 0)
|
||||
return r;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_get_resource);
|
||||
|
||||
/**
|
||||
* platform_get_irq - get an IRQ for a device
|
||||
* @dev: platform device
|
||||
* @num: IRQ number index
|
||||
*/
|
||||
int platform_get_irq(struct platform_device *dev, unsigned int num)
|
||||
{
|
||||
struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, num);
|
||||
|
||||
return r ? r->start : -ENXIO;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_get_irq);
|
||||
|
||||
/**
|
||||
* platform_get_resource_byname - get a resource for a device by name
|
||||
* @dev: platform device
|
||||
* @type: resource type
|
||||
* @name: resource name
|
||||
*/
|
||||
struct resource *
|
||||
platform_get_resource_byname(struct platform_device *dev, unsigned int type,
|
||||
char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < dev->num_resources; i++) {
|
||||
struct resource *r = &dev->resource[i];
|
||||
|
||||
if ((r->flags & (IORESOURCE_IO|IORESOURCE_MEM|
|
||||
IORESOURCE_IRQ|IORESOURCE_DMA)) == type)
|
||||
if (!strcmp(r->name, name))
|
||||
return r;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_get_resource_byname);
|
||||
|
||||
/**
|
||||
* platform_get_irq - get an IRQ for a device
|
||||
* @dev: platform device
|
||||
* @name: IRQ name
|
||||
*/
|
||||
int platform_get_irq_byname(struct platform_device *dev, char *name)
|
||||
{
|
||||
struct resource *r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name);
|
||||
|
||||
return r ? r->start : -ENXIO;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_get_irq_byname);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_add_devices);
|
||||
|
||||
struct platform_object {
|
||||
struct platform_device pdev;
|
||||
char name[1];
|
||||
};
|
||||
|
||||
/**
|
||||
* platform_device_put
|
||||
* @pdev: platform device to free
|
||||
*
|
||||
* Free all memory associated with a platform device. This function
|
||||
* must _only_ be externally called in error cases. All other usage
|
||||
* is a bug.
|
||||
*/
|
||||
void platform_device_put(struct platform_device *pdev)
|
||||
{
|
||||
if (pdev)
|
||||
put_device(&pdev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_put);
|
||||
|
||||
static void platform_device_release(struct device *dev)
|
||||
{
|
||||
struct platform_object *pa = container_of(dev, struct platform_object, pdev.dev);
|
||||
|
||||
kfree(pa->pdev.dev.platform_data);
|
||||
kfree(pa->pdev.resource);
|
||||
kfree(pa);
|
||||
}
|
||||
|
||||
/**
|
||||
* platform_device_alloc
|
||||
* @name: base name of the device we're adding
|
||||
* @id: instance id
|
||||
*
|
||||
* Create a platform device object which can have other objects attached
|
||||
* to it, and which will have attached objects freed when it is released.
|
||||
*/
|
||||
struct platform_device *platform_device_alloc(const char *name, unsigned int id)
|
||||
{
|
||||
struct platform_object *pa;
|
||||
|
||||
pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
|
||||
if (pa) {
|
||||
strcpy(pa->name, name);
|
||||
pa->pdev.name = pa->name;
|
||||
pa->pdev.id = id;
|
||||
device_initialize(&pa->pdev.dev);
|
||||
pa->pdev.dev.release = platform_device_release;
|
||||
}
|
||||
|
||||
return pa ? &pa->pdev : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_alloc);
|
||||
|
||||
/**
|
||||
* platform_device_add_resources
|
||||
* @pdev: platform device allocated by platform_device_alloc to add resources to
|
||||
* @res: set of resources that needs to be allocated for the device
|
||||
* @num: number of resources
|
||||
*
|
||||
* Add a copy of the resources to the platform device. The memory
|
||||
* associated with the resources will be freed when the platform
|
||||
* device is released.
|
||||
*/
|
||||
int platform_device_add_resources(struct platform_device *pdev, struct resource *res, unsigned int num)
|
||||
{
|
||||
struct resource *r;
|
||||
|
||||
r = kmalloc(sizeof(struct resource) * num, GFP_KERNEL);
|
||||
if (r) {
|
||||
memcpy(r, res, sizeof(struct resource) * num);
|
||||
pdev->resource = r;
|
||||
pdev->num_resources = num;
|
||||
}
|
||||
return r ? 0 : -ENOMEM;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_add_resources);
|
||||
|
||||
/**
|
||||
* platform_device_add_data
|
||||
* @pdev: platform device allocated by platform_device_alloc to add resources to
|
||||
* @data: platform specific data for this platform device
|
||||
* @size: size of platform specific data
|
||||
*
|
||||
* Add a copy of platform specific data to the platform device's platform_data
|
||||
* pointer. The memory associated with the platform data will be freed
|
||||
* when the platform device is released.
|
||||
*/
|
||||
int platform_device_add_data(struct platform_device *pdev, const void *data, size_t size)
|
||||
{
|
||||
void *d;
|
||||
|
||||
d = kmalloc(size, GFP_KERNEL);
|
||||
if (d) {
|
||||
memcpy(d, data, size);
|
||||
pdev->dev.platform_data = d;
|
||||
}
|
||||
return d ? 0 : -ENOMEM;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_add_data);
|
||||
|
||||
/**
|
||||
* 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 = 0;
|
||||
|
||||
if (!pdev)
|
||||
return -EINVAL;
|
||||
|
||||
if (!pdev->dev.parent)
|
||||
pdev->dev.parent = &platform_bus;
|
||||
|
||||
pdev->dev.bus = &platform_bus_type;
|
||||
|
||||
if (pdev->id != -1)
|
||||
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id);
|
||||
else
|
||||
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
|
||||
|
||||
for (i = 0; i < pdev->num_resources; i++) {
|
||||
struct resource *p, *r = &pdev->resource[i];
|
||||
|
||||
if (r->name == NULL)
|
||||
r->name = pdev->dev.bus_id;
|
||||
|
||||
p = r->parent;
|
||||
if (!p) {
|
||||
if (r->flags & IORESOURCE_MEM)
|
||||
p = &iomem_resource;
|
||||
else if (r->flags & IORESOURCE_IO)
|
||||
p = &ioport_resource;
|
||||
}
|
||||
|
||||
if (p && insert_resource(p, r)) {
|
||||
printk(KERN_ERR
|
||||
"%s: failed to claim resource %d\n",
|
||||
pdev->dev.bus_id, i);
|
||||
ret = -EBUSY;
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("Registering platform device '%s'. Parent at %s\n",
|
||||
pdev->dev.bus_id, pdev->dev.parent->bus_id);
|
||||
|
||||
ret = device_add(&pdev->dev);
|
||||
if (ret == 0)
|
||||
return ret;
|
||||
|
||||
failed:
|
||||
while (--i >= 0)
|
||||
if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
|
||||
release_resource(&pdev->resource[i]);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_add);
|
||||
|
||||
/**
|
||||
* platform_device_del - remove a platform-level device
|
||||
* @pdev: platform device we're removing
|
||||
*
|
||||
* Note that this function will also release all memory- and port-based
|
||||
* resources owned by the device (@dev->resource).
|
||||
*/
|
||||
void platform_device_del(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (pdev) {
|
||||
for (i = 0; i < pdev->num_resources; i++) {
|
||||
struct resource *r = &pdev->resource[i];
|
||||
if (r->flags & (IORESOURCE_MEM|IORESOURCE_IO))
|
||||
release_resource(r);
|
||||
}
|
||||
|
||||
device_del(&pdev->dev);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_del);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return platform_device_add(pdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_register);
|
||||
|
||||
/**
|
||||
* platform_device_unregister - unregister a platform-level device
|
||||
* @pdev: platform device we're unregistering
|
||||
*
|
||||
* Unregistration is done in 2 steps. First we release all resources
|
||||
* and remove it from the subsystem, then we drop reference count by
|
||||
* calling platform_device_put().
|
||||
*/
|
||||
void platform_device_unregister(struct platform_device * pdev)
|
||||
{
|
||||
platform_device_del(pdev);
|
||||
platform_device_put(pdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_unregister);
|
||||
|
||||
/**
|
||||
* platform_device_register_simple
|
||||
* @name: base name of the device we're adding
|
||||
* @id: instance id
|
||||
* @res: set of resources that needs to be allocated for the device
|
||||
* @num: number of resources
|
||||
*
|
||||
* This function creates a simple platform device that requires minimal
|
||||
* resource and memory management. Canned release function freeing
|
||||
* memory allocated for the device allows drivers using such devices
|
||||
* to be unloaded iwithout waiting for the last reference to the device
|
||||
* to be dropped.
|
||||
*/
|
||||
struct platform_device *platform_device_register_simple(char *name, unsigned int id,
|
||||
struct resource *res, unsigned int num)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int retval;
|
||||
|
||||
pdev = platform_device_alloc(name, id);
|
||||
if (!pdev) {
|
||||
retval = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (num) {
|
||||
retval = platform_device_add_resources(pdev, res, num);
|
||||
if (retval)
|
||||
goto error;
|
||||
}
|
||||
|
||||
retval = platform_device_add(pdev);
|
||||
if (retval)
|
||||
goto error;
|
||||
|
||||
return pdev;
|
||||
|
||||
error:
|
||||
platform_device_put(pdev);
|
||||
return ERR_PTR(retval);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_device_register_simple);
|
||||
|
||||
static int platform_drv_probe(struct device *_dev)
|
||||
{
|
||||
struct platform_driver *drv = to_platform_driver(_dev->driver);
|
||||
struct platform_device *dev = to_platform_device(_dev);
|
||||
|
||||
return drv->probe(dev);
|
||||
}
|
||||
|
||||
static int platform_drv_probe_fail(struct device *_dev)
|
||||
{
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static int platform_drv_remove(struct device *_dev)
|
||||
{
|
||||
struct platform_driver *drv = to_platform_driver(_dev->driver);
|
||||
struct platform_device *dev = to_platform_device(_dev);
|
||||
|
||||
return drv->remove(dev);
|
||||
}
|
||||
|
||||
static void platform_drv_shutdown(struct device *_dev)
|
||||
{
|
||||
struct platform_driver *drv = to_platform_driver(_dev->driver);
|
||||
struct platform_device *dev = to_platform_device(_dev);
|
||||
|
||||
drv->shutdown(dev);
|
||||
}
|
||||
|
||||
static int platform_drv_suspend(struct device *_dev, pm_message_t state)
|
||||
{
|
||||
struct platform_driver *drv = to_platform_driver(_dev->driver);
|
||||
struct platform_device *dev = to_platform_device(_dev);
|
||||
|
||||
return drv->suspend(dev, state);
|
||||
}
|
||||
|
||||
static int platform_drv_resume(struct device *_dev)
|
||||
{
|
||||
struct platform_driver *drv = to_platform_driver(_dev->driver);
|
||||
struct platform_device *dev = to_platform_device(_dev);
|
||||
|
||||
return drv->resume(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* platform_driver_register
|
||||
* @drv: platform driver structure
|
||||
*/
|
||||
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;
|
||||
if (drv->suspend)
|
||||
drv->driver.suspend = platform_drv_suspend;
|
||||
if (drv->resume)
|
||||
drv->driver.resume = platform_drv_resume;
|
||||
return driver_register(&drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_driver_register);
|
||||
|
||||
/**
|
||||
* platform_driver_unregister
|
||||
* @drv: platform driver structure
|
||||
*/
|
||||
void platform_driver_unregister(struct platform_driver *drv)
|
||||
{
|
||||
driver_unregister(&drv->driver);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_driver_unregister);
|
||||
|
||||
/**
|
||||
* platform_driver_probe - register driver for non-hotpluggable device
|
||||
* @drv: platform driver structure
|
||||
* @probe: the driver probe routine, probably from an __init section
|
||||
*
|
||||
* Use this instead of platform_driver_register() when you know the device
|
||||
* is not hotpluggable and has already been registered, and you want to
|
||||
* remove its run-once probe() infrastructure from memory after the driver
|
||||
* has bound to the device.
|
||||
*
|
||||
* One typical use for this would be with drivers for controllers integrated
|
||||
* into system-on-chip processors, where the controller devices have been
|
||||
* configured as part of board setup.
|
||||
*
|
||||
* Returns zero if the driver registered and bound to a device, else returns
|
||||
* a negative error code and with the driver not registered.
|
||||
*/
|
||||
int __init_or_module platform_driver_probe(struct platform_driver *drv,
|
||||
int (*probe)(struct platform_device *))
|
||||
{
|
||||
int retval, code;
|
||||
|
||||
/* temporary section violation during probe() */
|
||||
drv->probe = probe;
|
||||
retval = code = platform_driver_register(drv);
|
||||
|
||||
/* Fixup that section violation, being paranoid about code scanning
|
||||
* the list of drivers in order to probe new devices. Check to see
|
||||
* if the probe was successful, and make sure any forced probes of
|
||||
* new devices fail.
|
||||
*/
|
||||
spin_lock(&platform_bus_type.klist_drivers.k_lock);
|
||||
drv->probe = NULL;
|
||||
if (code == 0 && list_empty(&drv->driver.klist_devices.k_list))
|
||||
retval = -ENODEV;
|
||||
drv->driver.probe = platform_drv_probe_fail;
|
||||
spin_unlock(&platform_bus_type.klist_drivers.k_lock);
|
||||
|
||||
if (code != retval)
|
||||
platform_driver_unregister(drv);
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(platform_driver_probe);
|
||||
|
||||
/* modalias support enables more hands-off userspace setup:
|
||||
* (a) environment variable lets new-style hotplug events work once system is
|
||||
* fully running: "modprobe $MODALIAS"
|
||||
* (b) sysfs attribute lets new-style coldplug recover from hotplug events
|
||||
* mishandled before system is fully running: "modprobe $(cat modalias)"
|
||||
*/
|
||||
static ssize_t
|
||||
modalias_show(struct device *dev, struct device_attribute *a, char *buf)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
int len = snprintf(buf, PAGE_SIZE, "%s\n", pdev->name);
|
||||
|
||||
return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
|
||||
}
|
||||
|
||||
static struct device_attribute platform_dev_attrs[] = {
|
||||
__ATTR_RO(modalias),
|
||||
__ATTR_NULL,
|
||||
};
|
||||
|
||||
static int platform_uevent(struct device *dev, char **envp, int num_envp,
|
||||
char *buffer, int buffer_size)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
|
||||
envp[0] = buffer;
|
||||
snprintf(buffer, buffer_size, "MODALIAS=%s", pdev->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* platform_match - bind platform device to platform driver.
|
||||
* @dev: device.
|
||||
* @drv: driver.
|
||||
*
|
||||
* Platform device IDs are assumed to be encoded like this:
|
||||
* "<name><instance>", where <name> is a short description of the
|
||||
* type of device, like "pci" or "floppy", and <instance> is the
|
||||
* enumerated instance of the device, like '0' or '42'.
|
||||
* Driver IDs are simply "<name>".
|
||||
* So, extract the <name> from the platform_device structure,
|
||||
* and compare it against the name of the driver. Return whether
|
||||
* they match or not.
|
||||
*/
|
||||
|
||||
static int platform_match(struct device * dev, struct device_driver * drv)
|
||||
{
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
|
||||
|
||||
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
|
||||
}
|
||||
|
||||
static int platform_suspend(struct device *dev, pm_message_t mesg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && dev->driver->suspend)
|
||||
ret = dev->driver->suspend(dev, mesg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int platform_suspend_late(struct device *dev, pm_message_t mesg)
|
||||
{
|
||||
struct platform_driver *drv = to_platform_driver(dev->driver);
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->suspend_late)
|
||||
ret = drv->suspend_late(pdev, mesg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int platform_resume_early(struct device *dev)
|
||||
{
|
||||
struct platform_driver *drv = to_platform_driver(dev->driver);
|
||||
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && drv->resume_early)
|
||||
ret = drv->resume_early(pdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int platform_resume(struct device * dev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (dev->driver && dev->driver->resume)
|
||||
ret = dev->driver->resume(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct bus_type platform_bus_type = {
|
||||
.name = "platform",
|
||||
.dev_attrs = platform_dev_attrs,
|
||||
.match = platform_match,
|
||||
.uevent = platform_uevent,
|
||||
.suspend = platform_suspend,
|
||||
.suspend_late = platform_suspend_late,
|
||||
.resume_early = platform_resume_early,
|
||||
.resume = platform_resume,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(platform_bus_type);
|
||||
|
||||
int __init platform_bus_init(void)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = device_register(&platform_bus);
|
||||
if (error)
|
||||
return error;
|
||||
error = bus_register(&platform_bus_type);
|
||||
if (error)
|
||||
device_unregister(&platform_bus);
|
||||
return error;
|
||||
}
|
||||
|
||||
#ifndef ARCH_HAS_DMA_GET_REQUIRED_MASK
|
||||
u64 dma_get_required_mask(struct device *dev)
|
||||
{
|
||||
u32 low_totalram = ((max_pfn - 1) << PAGE_SHIFT);
|
||||
u32 high_totalram = ((max_pfn - 1) >> (32 - PAGE_SHIFT));
|
||||
u64 mask;
|
||||
|
||||
if (!high_totalram) {
|
||||
/* convert to mask just covering totalram */
|
||||
low_totalram = (1 << (fls(low_totalram) - 1));
|
||||
low_totalram += low_totalram - 1;
|
||||
mask = low_totalram;
|
||||
} else {
|
||||
high_totalram = (1 << (fls(high_totalram) - 1));
|
||||
high_totalram += high_totalram - 1;
|
||||
mask = (((u64)high_totalram) << 32) + 0xffffffff;
|
||||
}
|
||||
return mask & *dev->dma_mask;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dma_get_required_mask);
|
||||
#endif
|
||||
10
drivers/base/power/Makefile
Normal file
10
drivers/base/power/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
obj-y := shutdown.o
|
||||
obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
|
||||
obj-$(CONFIG_PM_TRACE) += trace.o
|
||||
|
||||
ifeq ($(CONFIG_DEBUG_DRIVER),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
ifeq ($(CONFIG_PM_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
80
drivers/base/power/main.c
Normal file
80
drivers/base/power/main.c
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* drivers/base/power/main.c - Where the driver meets power management.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Lab
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*
|
||||
* The driver model core calls device_pm_add() when a device is registered.
|
||||
* This will intialize the embedded device_pm_info object in the device
|
||||
* and add it to the list of power-controlled devices. sysfs entries for
|
||||
* controlling device power management will also be added.
|
||||
*
|
||||
* A different set of lists than the global subsystem list are used to
|
||||
* keep track of power info because we use different lists to hold
|
||||
* devices based on what stage of the power management process they
|
||||
* are in. The power domain dependencies may also differ from the
|
||||
* ancestral dependencies that the subsystem list maintains.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include "power.h"
|
||||
|
||||
LIST_HEAD(dpm_active);
|
||||
LIST_HEAD(dpm_off);
|
||||
LIST_HEAD(dpm_off_irq);
|
||||
|
||||
DECLARE_MUTEX(dpm_sem);
|
||||
DECLARE_MUTEX(dpm_list_sem);
|
||||
|
||||
/**
|
||||
* device_pm_set_parent - Specify power dependency.
|
||||
* @dev: Device who needs power.
|
||||
* @parent: Device that supplies power.
|
||||
*
|
||||
* This function is used to manually describe a power-dependency
|
||||
* relationship. It may be used to specify a transversal relationship
|
||||
* (where the power supplier is not the physical (or electrical)
|
||||
* ancestor of a specific device.
|
||||
* The effect of this is that the supplier will not be powered down
|
||||
* before the power dependent.
|
||||
*/
|
||||
|
||||
void device_pm_set_parent(struct device * dev, struct device * parent)
|
||||
{
|
||||
put_device(dev->power.pm_parent);
|
||||
dev->power.pm_parent = get_device(parent);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_pm_set_parent);
|
||||
|
||||
int device_pm_add(struct device * dev)
|
||||
{
|
||||
int error;
|
||||
|
||||
pr_debug("PM: Adding info for %s:%s\n",
|
||||
dev->bus ? dev->bus->name : "No Bus",
|
||||
kobject_name(&dev->kobj));
|
||||
down(&dpm_list_sem);
|
||||
list_add_tail(&dev->power.entry, &dpm_active);
|
||||
device_pm_set_parent(dev, dev->parent);
|
||||
if ((error = dpm_sysfs_add(dev)))
|
||||
list_del(&dev->power.entry);
|
||||
up(&dpm_list_sem);
|
||||
return error;
|
||||
}
|
||||
|
||||
void device_pm_remove(struct device * dev)
|
||||
{
|
||||
pr_debug("PM: Removing info for %s:%s\n",
|
||||
dev->bus ? dev->bus->name : "No Bus",
|
||||
kobject_name(&dev->kobj));
|
||||
down(&dpm_list_sem);
|
||||
dpm_sysfs_remove(dev);
|
||||
put_device(dev->power.pm_parent);
|
||||
list_del_init(&dev->power.entry);
|
||||
up(&dpm_list_sem);
|
||||
}
|
||||
|
||||
|
||||
82
drivers/base/power/power.h
Normal file
82
drivers/base/power/power.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* shutdown.c
|
||||
*/
|
||||
|
||||
extern void device_shutdown(void);
|
||||
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/*
|
||||
* main.c
|
||||
*/
|
||||
|
||||
/*
|
||||
* Used to synchronize global power management operations.
|
||||
*/
|
||||
extern struct semaphore dpm_sem;
|
||||
|
||||
/*
|
||||
* Used to serialize changes to the dpm_* lists.
|
||||
*/
|
||||
extern struct semaphore dpm_list_sem;
|
||||
|
||||
/*
|
||||
* The PM lists.
|
||||
*/
|
||||
extern struct list_head dpm_active;
|
||||
extern struct list_head dpm_off;
|
||||
extern struct list_head dpm_off_irq;
|
||||
|
||||
|
||||
static inline struct dev_pm_info * to_pm_info(struct list_head * entry)
|
||||
{
|
||||
return container_of(entry, struct dev_pm_info, entry);
|
||||
}
|
||||
|
||||
static inline struct device * to_device(struct list_head * entry)
|
||||
{
|
||||
return container_of(to_pm_info(entry), struct device, power);
|
||||
}
|
||||
|
||||
extern int device_pm_add(struct device *);
|
||||
extern void device_pm_remove(struct device *);
|
||||
|
||||
/*
|
||||
* sysfs.c
|
||||
*/
|
||||
|
||||
extern int dpm_sysfs_add(struct device *);
|
||||
extern void dpm_sysfs_remove(struct device *);
|
||||
|
||||
/*
|
||||
* resume.c
|
||||
*/
|
||||
|
||||
extern void dpm_resume(void);
|
||||
extern void dpm_power_up(void);
|
||||
extern int resume_device(struct device *);
|
||||
|
||||
/*
|
||||
* suspend.c
|
||||
*/
|
||||
extern int suspend_device(struct device *, pm_message_t);
|
||||
|
||||
|
||||
/*
|
||||
* runtime.c
|
||||
*/
|
||||
|
||||
#else /* CONFIG_PM */
|
||||
|
||||
|
||||
static inline int device_pm_add(struct device * dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void device_pm_remove(struct device * dev)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
147
drivers/base/power/resume.c
Normal file
147
drivers/base/power/resume.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* resume.c - Functions for waking devices up.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/resume-trace.h>
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
|
||||
/**
|
||||
* resume_device - Restore state for one device.
|
||||
* @dev: Device.
|
||||
*
|
||||
*/
|
||||
|
||||
int resume_device(struct device * dev)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
TRACE_DEVICE(dev);
|
||||
TRACE_RESUME(0);
|
||||
down(&dev->sem);
|
||||
if (dev->power.pm_parent
|
||||
&& dev->power.pm_parent->power.power_state.event) {
|
||||
dev_err(dev, "PM: resume from %d, parent %s still %d\n",
|
||||
dev->power.power_state.event,
|
||||
dev->power.pm_parent->bus_id,
|
||||
dev->power.pm_parent->power.power_state.event);
|
||||
}
|
||||
if (dev->bus && dev->bus->resume) {
|
||||
dev_dbg(dev,"resuming\n");
|
||||
error = dev->bus->resume(dev);
|
||||
}
|
||||
if (dev->class && dev->class->resume) {
|
||||
dev_dbg(dev,"class resume\n");
|
||||
error = dev->class->resume(dev);
|
||||
}
|
||||
up(&dev->sem);
|
||||
TRACE_RESUME(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static int resume_device_early(struct device * dev)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
TRACE_DEVICE(dev);
|
||||
TRACE_RESUME(0);
|
||||
if (dev->bus && dev->bus->resume_early) {
|
||||
dev_dbg(dev,"EARLY resume\n");
|
||||
error = dev->bus->resume_early(dev);
|
||||
}
|
||||
TRACE_RESUME(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume the devices that have either not gone through
|
||||
* the late suspend, or that did go through it but also
|
||||
* went through the early resume
|
||||
*/
|
||||
void dpm_resume(void)
|
||||
{
|
||||
down(&dpm_list_sem);
|
||||
while(!list_empty(&dpm_off)) {
|
||||
struct list_head * entry = dpm_off.next;
|
||||
struct device * dev = to_device(entry);
|
||||
|
||||
get_device(dev);
|
||||
list_move_tail(entry, &dpm_active);
|
||||
|
||||
up(&dpm_list_sem);
|
||||
if (!dev->power.prev_state.event)
|
||||
resume_device(dev);
|
||||
down(&dpm_list_sem);
|
||||
put_device(dev);
|
||||
}
|
||||
up(&dpm_list_sem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* device_resume - Restore state of each device in system.
|
||||
*
|
||||
* Walk the dpm_off list, remove each entry, resume the device,
|
||||
* then add it to the dpm_active list.
|
||||
*/
|
||||
|
||||
void device_resume(void)
|
||||
{
|
||||
might_sleep();
|
||||
down(&dpm_sem);
|
||||
dpm_resume();
|
||||
up(&dpm_sem);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_resume);
|
||||
|
||||
|
||||
/**
|
||||
* dpm_power_up - Power on some devices.
|
||||
*
|
||||
* Walk the dpm_off_irq list and power each device up. This
|
||||
* is used for devices that required they be powered down with
|
||||
* interrupts disabled. As devices are powered on, they are moved
|
||||
* to the dpm_active list.
|
||||
*
|
||||
* Interrupts must be disabled when calling this.
|
||||
*/
|
||||
|
||||
void dpm_power_up(void)
|
||||
{
|
||||
while(!list_empty(&dpm_off_irq)) {
|
||||
struct list_head * entry = dpm_off_irq.next;
|
||||
struct device * dev = to_device(entry);
|
||||
|
||||
list_move_tail(entry, &dpm_off);
|
||||
resume_device_early(dev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* device_power_up - Turn on all devices that need special attention.
|
||||
*
|
||||
* Power on system devices then devices that required we shut them down
|
||||
* with interrupts disabled.
|
||||
* Called with interrupts disabled.
|
||||
*/
|
||||
|
||||
void device_power_up(void)
|
||||
{
|
||||
sysdev_resume();
|
||||
dpm_power_up();
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_power_up);
|
||||
|
||||
|
||||
85
drivers/base/power/runtime.c
Normal file
85
drivers/base/power/runtime.c
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* drivers/base/power/runtime.c - Handling dynamic device power management.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Lab
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include "power.h"
|
||||
|
||||
|
||||
static void runtime_resume(struct device * dev)
|
||||
{
|
||||
dev_dbg(dev, "resuming\n");
|
||||
if (!dev->power.power_state.event)
|
||||
return;
|
||||
if (!resume_device(dev))
|
||||
dev->power.power_state = PMSG_ON;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dpm_runtime_resume - Power one device back on.
|
||||
* @dev: Device.
|
||||
*
|
||||
* Bring one device back to the on state by first powering it
|
||||
* on, then restoring state. We only operate on devices that aren't
|
||||
* already on.
|
||||
* FIXME: We need to handle devices that are in an unknown state.
|
||||
*/
|
||||
|
||||
void dpm_runtime_resume(struct device * dev)
|
||||
{
|
||||
down(&dpm_sem);
|
||||
runtime_resume(dev);
|
||||
up(&dpm_sem);
|
||||
}
|
||||
EXPORT_SYMBOL(dpm_runtime_resume);
|
||||
|
||||
|
||||
/**
|
||||
* dpm_runtime_suspend - Put one device in low-power state.
|
||||
* @dev: Device.
|
||||
* @state: State to enter.
|
||||
*/
|
||||
|
||||
int dpm_runtime_suspend(struct device * dev, pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
down(&dpm_sem);
|
||||
if (dev->power.power_state.event == state.event)
|
||||
goto Done;
|
||||
|
||||
if (dev->power.power_state.event)
|
||||
runtime_resume(dev);
|
||||
|
||||
if (!(error = suspend_device(dev, state)))
|
||||
dev->power.power_state = state;
|
||||
Done:
|
||||
up(&dpm_sem);
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL(dpm_runtime_suspend);
|
||||
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* dpm_set_power_state - Update power_state field.
|
||||
* @dev: Device.
|
||||
* @state: Power state device is in.
|
||||
*
|
||||
* This is an update mechanism for drivers to notify the core
|
||||
* what power state a device is in. Device probing code may not
|
||||
* always be able to tell, but we need accurate information to
|
||||
* work reliably.
|
||||
*/
|
||||
void dpm_set_power_state(struct device * dev, pm_message_t state)
|
||||
{
|
||||
down(&dpm_sem);
|
||||
dev->power.power_state = state;
|
||||
up(&dpm_sem);
|
||||
}
|
||||
#endif /* 0 */
|
||||
54
drivers/base/power/shutdown.c
Normal file
54
drivers/base/power/shutdown.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* shutdown.c - power management functions for the device tree.
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* 2002-3 Open Source Development Lab
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <asm/semaphore.h>
|
||||
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
#define to_dev(node) container_of(node, struct device, kobj.entry)
|
||||
|
||||
extern struct subsystem devices_subsys;
|
||||
|
||||
|
||||
/**
|
||||
* We handle system devices differently - we suspend and shut them
|
||||
* down last and resume them first. That way, we don't do anything stupid like
|
||||
* shutting down the interrupt controller before any devices..
|
||||
*
|
||||
* Note that there are not different stages for power management calls -
|
||||
* they only get one called once when interrupts are disabled.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* device_shutdown - call ->shutdown() on each device to shutdown.
|
||||
*/
|
||||
void device_shutdown(void)
|
||||
{
|
||||
struct device * dev, *devn;
|
||||
|
||||
down_write(&devices_subsys.rwsem);
|
||||
list_for_each_entry_safe_reverse(dev, devn, &devices_subsys.kset.list,
|
||||
kobj.entry) {
|
||||
if (dev->bus && dev->bus->shutdown) {
|
||||
dev_dbg(dev, "shutdown\n");
|
||||
dev->bus->shutdown(dev);
|
||||
} else if (dev->driver && dev->driver->shutdown) {
|
||||
dev_dbg(dev, "shutdown\n");
|
||||
dev->driver->shutdown(dev);
|
||||
}
|
||||
}
|
||||
up_write(&devices_subsys.rwsem);
|
||||
|
||||
sysdev_shutdown();
|
||||
}
|
||||
|
||||
237
drivers/base/power/suspend.c
Normal file
237
drivers/base/power/suspend.c
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* suspend.c - Functions for putting devices to sleep.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/pm.h>
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
#ifdef CONFIG_PM_CPU_MODE
|
||||
extern unsigned char pm_cpu_mode;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The entries in the dpm_active list are in a depth first order, simply
|
||||
* because children are guaranteed to be discovered after parents, and
|
||||
* are inserted at the back of the list on discovery.
|
||||
*
|
||||
* All list on the suspend path are done in reverse order, so we operate
|
||||
* on the leaves of the device tree (or forests, depending on how you want
|
||||
* to look at it ;) first. As nodes are removed from the back of the list,
|
||||
* they are inserted into the front of their destintation lists.
|
||||
*
|
||||
* Things are the reverse on the resume path - iterations are done in
|
||||
* forward order, and nodes are inserted at the back of their destination
|
||||
* lists. This way, the ancestors will be accessed before their descendents.
|
||||
*/
|
||||
|
||||
static inline char *suspend_verb(u32 event)
|
||||
{
|
||||
switch (event) {
|
||||
case PM_EVENT_SUSPEND: return "suspend";
|
||||
case PM_EVENT_FREEZE: return "freeze";
|
||||
case PM_EVENT_PRETHAW: return "prethaw";
|
||||
default: return "(unknown suspend event)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* suspend_device - Save state of one device.
|
||||
* @dev: Device.
|
||||
* @state: Power state device is entering.
|
||||
*/
|
||||
|
||||
int suspend_device(struct device * dev, pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
down(&dev->sem);
|
||||
if (dev->power.power_state.event) {
|
||||
dev_dbg(dev, "PM: suspend %d-->%d\n",
|
||||
dev->power.power_state.event, state.event);
|
||||
}
|
||||
if (dev->power.pm_parent
|
||||
&& dev->power.pm_parent->power.power_state.event) {
|
||||
dev_err(dev,
|
||||
"PM: suspend %d->%d, parent %s already %d\n",
|
||||
dev->power.power_state.event, state.event,
|
||||
dev->power.pm_parent->bus_id,
|
||||
dev->power.pm_parent->power.power_state.event);
|
||||
}
|
||||
|
||||
dev->power.prev_state = dev->power.power_state;
|
||||
|
||||
if (dev->class && dev->class->suspend && !dev->power.power_state.event) {
|
||||
dev_dbg(dev, "class %s%s\n",
|
||||
suspend_verb(state.event),
|
||||
((state.event == PM_EVENT_SUSPEND)
|
||||
&& device_may_wakeup(dev))
|
||||
? ", may wakeup"
|
||||
: ""
|
||||
);
|
||||
error = dev->class->suspend(dev, state);
|
||||
suspend_report_result(dev->class->suspend, error);
|
||||
}
|
||||
|
||||
if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) {
|
||||
dev_dbg(dev, "%s%s\n",
|
||||
suspend_verb(state.event),
|
||||
((state.event == PM_EVENT_SUSPEND)
|
||||
&& device_may_wakeup(dev))
|
||||
? ", may wakeup"
|
||||
: ""
|
||||
);
|
||||
error = dev->bus->suspend(dev, state);
|
||||
suspend_report_result(dev->bus->suspend, error);
|
||||
}
|
||||
up(&dev->sem);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is called with interrupts off, only a single CPU
|
||||
* running. We can't do down() on a semaphore (and we don't
|
||||
* need the protection)
|
||||
*/
|
||||
static int suspend_device_late(struct device *dev, pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) {
|
||||
dev_dbg(dev, "LATE %s%s\n",
|
||||
suspend_verb(state.event),
|
||||
((state.event == PM_EVENT_SUSPEND)
|
||||
&& device_may_wakeup(dev))
|
||||
? ", may wakeup"
|
||||
: ""
|
||||
);
|
||||
error = dev->bus->suspend_late(dev, state);
|
||||
suspend_report_result(dev->bus->suspend_late, error);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* device_suspend - Save state and stop all devices in system.
|
||||
* @state: Power state to put each device in.
|
||||
*
|
||||
* Walk the dpm_active list, call ->suspend() for each device, and move
|
||||
* it to the dpm_off list.
|
||||
*
|
||||
* (For historical reasons, if it returns -EAGAIN, that used to mean
|
||||
* that the device would be called again with interrupts disabled.
|
||||
* These days, we use the "suspend_late()" callback for that, so we
|
||||
* print a warning and consider it an error).
|
||||
*
|
||||
* If we get a different error, try and back out.
|
||||
*
|
||||
* If we hit a failure with any of the devices, call device_resume()
|
||||
* above to bring the suspended devices back to life.
|
||||
*
|
||||
*/
|
||||
|
||||
int device_suspend(pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
might_sleep();
|
||||
down(&dpm_sem);
|
||||
down(&dpm_list_sem);
|
||||
while (!list_empty(&dpm_active) && error == 0) {
|
||||
struct list_head * entry = dpm_active.prev;
|
||||
struct device * dev = to_device(entry);
|
||||
|
||||
get_device(dev);
|
||||
up(&dpm_list_sem);
|
||||
|
||||
#ifdef CONFIG_PM_CPU_MODE
|
||||
if(pm_cpu_mode == 0){
|
||||
error = suspend_device(dev, state);
|
||||
}
|
||||
else{
|
||||
// suspend_device(dev, state);
|
||||
}
|
||||
#else
|
||||
error = suspend_device(dev, state);
|
||||
#endif
|
||||
|
||||
down(&dpm_list_sem);
|
||||
|
||||
/* Check if the device got removed */
|
||||
if (!list_empty(&dev->power.entry)) {
|
||||
/* Move it to the dpm_off list */
|
||||
if (!error)
|
||||
list_move(&dev->power.entry, &dpm_off);
|
||||
}
|
||||
if (error)
|
||||
printk(KERN_ERR "Could not suspend device %s: "
|
||||
"error %d%s\n",
|
||||
kobject_name(&dev->kobj), error,
|
||||
error == -EAGAIN ? " (please convert to suspend_late)" : "");
|
||||
put_device(dev);
|
||||
}
|
||||
up(&dpm_list_sem);
|
||||
if (error)
|
||||
dpm_resume();
|
||||
|
||||
up(&dpm_sem);
|
||||
return error;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_suspend);
|
||||
|
||||
/**
|
||||
* device_power_down - Shut down special devices.
|
||||
* @state: Power state to enter.
|
||||
*
|
||||
* Walk the dpm_off_irq list, calling ->power_down() for each device that
|
||||
* couldn't power down the device with interrupts enabled. When we're
|
||||
* done, power down system devices.
|
||||
*/
|
||||
|
||||
int device_power_down(pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
struct device * dev;
|
||||
|
||||
while (!list_empty(&dpm_off)) {
|
||||
struct list_head * entry = dpm_off.prev;
|
||||
|
||||
dev = to_device(entry);
|
||||
error = suspend_device_late(dev, state);
|
||||
if (error)
|
||||
goto Error;
|
||||
list_move(&dev->power.entry, &dpm_off_irq);
|
||||
}
|
||||
|
||||
error = sysdev_suspend(state);
|
||||
Done:
|
||||
return error;
|
||||
Error:
|
||||
printk(KERN_ERR "Could not power down device %s: "
|
||||
"error %d\n", kobject_name(&dev->kobj), error);
|
||||
dpm_power_up();
|
||||
goto Done;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_power_down);
|
||||
|
||||
void __suspend_report_result(const char *function, void *fn, int ret)
|
||||
{
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s(): ", function);
|
||||
print_fn_descriptor_symbol("%s() returns ", (unsigned long)fn);
|
||||
printk("%d\n", ret);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__suspend_report_result);
|
||||
165
drivers/base/power/sysfs.c
Normal file
165
drivers/base/power/sysfs.c
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* drivers/base/power/sysfs.c - sysfs entries for device PM
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/string.h>
|
||||
#include "power.h"
|
||||
|
||||
|
||||
#ifdef CONFIG_PM_SYSFS_DEPRECATED
|
||||
|
||||
/**
|
||||
* state - Control current power state of device
|
||||
*
|
||||
* show() returns the current power state of the device. '0' indicates
|
||||
* the device is on. Other values (2) indicate the device is in some low
|
||||
* power state.
|
||||
*
|
||||
* store() sets the current power state, which is an integer valued
|
||||
* 0, 2, or 3. Devices with bus.suspend_late(), or bus.resume_early()
|
||||
* methods fail this operation; those methods couldn't be called.
|
||||
* Otherwise,
|
||||
*
|
||||
* - If the recorded dev->power.power_state.event matches the
|
||||
* target value, nothing is done.
|
||||
* - If the recorded event code is nonzero, the device is reactivated
|
||||
* by calling bus.resume() and/or class.resume().
|
||||
* - If the target value is nonzero, the device is suspended by
|
||||
* calling class.suspend() and/or bus.suspend() with event code
|
||||
* PM_EVENT_SUSPEND.
|
||||
*
|
||||
* This mechanism is DEPRECATED and should only be used for testing.
|
||||
*/
|
||||
|
||||
static ssize_t state_show(struct device * dev, struct device_attribute *attr, char * buf)
|
||||
{
|
||||
if (dev->power.power_state.event)
|
||||
return sprintf(buf, "2\n");
|
||||
else
|
||||
return sprintf(buf, "0\n");
|
||||
}
|
||||
|
||||
static ssize_t state_store(struct device * dev, struct device_attribute *attr, const char * buf, size_t n)
|
||||
{
|
||||
pm_message_t state;
|
||||
int error = -EINVAL;
|
||||
|
||||
/* disallow incomplete suspend sequences */
|
||||
if (dev->bus && (dev->bus->suspend_late || dev->bus->resume_early))
|
||||
return error;
|
||||
|
||||
state.event = PM_EVENT_SUSPEND;
|
||||
/* Older apps expected to write "3" here - confused with PCI D3 */
|
||||
if ((n == 1) && !strcmp(buf, "3"))
|
||||
error = dpm_runtime_suspend(dev, state);
|
||||
|
||||
if ((n == 1) && !strcmp(buf, "2"))
|
||||
error = dpm_runtime_suspend(dev, state);
|
||||
|
||||
if ((n == 1) && !strcmp(buf, "0")) {
|
||||
dpm_runtime_resume(dev);
|
||||
error = 0;
|
||||
}
|
||||
|
||||
return error ? error : n;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(state, 0644, state_show, state_store);
|
||||
|
||||
|
||||
#endif /* CONFIG_PM_SYSFS_DEPRECATED */
|
||||
|
||||
/*
|
||||
* wakeup - Report/change current wakeup option for device
|
||||
*
|
||||
* Some devices support "wakeup" events, which are hardware signals
|
||||
* used to activate devices from suspended or low power states. Such
|
||||
* devices have one of three values for the sysfs power/wakeup file:
|
||||
*
|
||||
* + "enabled\n" to issue the events;
|
||||
* + "disabled\n" not to do so; or
|
||||
* + "\n" for temporary or permanent inability to issue wakeup.
|
||||
*
|
||||
* (For example, unconfigured USB devices can't issue wakeups.)
|
||||
*
|
||||
* Familiar examples of devices that can issue wakeup events include
|
||||
* keyboards and mice (both PS2 and USB styles), power buttons, modems,
|
||||
* "Wake-On-LAN" Ethernet links, GPIO lines, and more. Some events
|
||||
* will wake the entire system from a suspend state; others may just
|
||||
* wake up the device (if the system as a whole is already active).
|
||||
* Some wakeup events use normal IRQ lines; other use special out
|
||||
* of band signaling.
|
||||
*
|
||||
* It is the responsibility of device drivers to enable (or disable)
|
||||
* wakeup signaling as part of changing device power states, respecting
|
||||
* the policy choices provided through the driver model.
|
||||
*
|
||||
* Devices may not be able to generate wakeup events from all power
|
||||
* states. Also, the events may be ignored in some configurations;
|
||||
* for example, they might need help from other devices that aren't
|
||||
* active, or which may have wakeup disabled. Some drivers rely on
|
||||
* wakeup events internally (unless they are disabled), keeping
|
||||
* their hardware in low power modes whenever they're unused. This
|
||||
* saves runtime power, without requiring system-wide sleep states.
|
||||
*/
|
||||
|
||||
static const char enabled[] = "enabled";
|
||||
static const char disabled[] = "disabled";
|
||||
|
||||
static ssize_t
|
||||
wake_show(struct device * dev, struct device_attribute *attr, char * buf)
|
||||
{
|
||||
return sprintf(buf, "%s\n", device_can_wakeup(dev)
|
||||
? (device_may_wakeup(dev) ? enabled : disabled)
|
||||
: "");
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
wake_store(struct device * dev, struct device_attribute *attr,
|
||||
const char * buf, size_t n)
|
||||
{
|
||||
char *cp;
|
||||
int len = n;
|
||||
|
||||
if (!device_can_wakeup(dev))
|
||||
return -EINVAL;
|
||||
|
||||
cp = memchr(buf, '\n', n);
|
||||
if (cp)
|
||||
len = cp - buf;
|
||||
if (len == sizeof enabled - 1
|
||||
&& strncmp(buf, enabled, sizeof enabled - 1) == 0)
|
||||
device_set_wakeup_enable(dev, 1);
|
||||
else if (len == sizeof disabled - 1
|
||||
&& strncmp(buf, disabled, sizeof disabled - 1) == 0)
|
||||
device_set_wakeup_enable(dev, 0);
|
||||
else
|
||||
return -EINVAL;
|
||||
return n;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store);
|
||||
|
||||
|
||||
static struct attribute * power_attrs[] = {
|
||||
#ifdef CONFIG_PM_SYSFS_DEPRECATED
|
||||
&dev_attr_state.attr,
|
||||
#endif
|
||||
&dev_attr_wakeup.attr,
|
||||
NULL,
|
||||
};
|
||||
static struct attribute_group pm_attr_group = {
|
||||
.name = "power",
|
||||
.attrs = power_attrs,
|
||||
};
|
||||
|
||||
int dpm_sysfs_add(struct device * dev)
|
||||
{
|
||||
return sysfs_create_group(&dev->kobj, &pm_attr_group);
|
||||
}
|
||||
|
||||
void dpm_sysfs_remove(struct device * dev)
|
||||
{
|
||||
sysfs_remove_group(&dev->kobj, &pm_attr_group);
|
||||
}
|
||||
228
drivers/base/power/trace.c
Normal file
228
drivers/base/power/trace.c
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* drivers/base/power/trace.c
|
||||
*
|
||||
* Copyright (C) 2006 Linus Torvalds
|
||||
*
|
||||
* Trace facility for suspend/resume problems, when none of the
|
||||
* devices may be working.
|
||||
*/
|
||||
|
||||
#include <linux/resume-trace.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
#include <asm/rtc.h>
|
||||
|
||||
#include "power.h"
|
||||
|
||||
/*
|
||||
* Horrid, horrid, horrid.
|
||||
*
|
||||
* It turns out that the _only_ piece of hardware that actually
|
||||
* keeps its value across a hard boot (and, more importantly, the
|
||||
* POST init sequence) is literally the realtime clock.
|
||||
*
|
||||
* Never mind that an RTC chip has 114 bytes (and often a whole
|
||||
* other bank of an additional 128 bytes) of nice SRAM that is
|
||||
* _designed_ to keep data - the POST will clear it. So we literally
|
||||
* can just use the few bytes of actual time data, which means that
|
||||
* we're really limited.
|
||||
*
|
||||
* It means, for example, that we can't use the seconds at all
|
||||
* (since the time between the hang and the boot might be more
|
||||
* than a minute), and we'd better not depend on the low bits of
|
||||
* the minutes either.
|
||||
*
|
||||
* There are the wday fields etc, but I wouldn't guarantee those
|
||||
* are dependable either. And if the date isn't valid, either the
|
||||
* hw or POST will do strange things.
|
||||
*
|
||||
* So we're left with:
|
||||
* - year: 0-99
|
||||
* - month: 0-11
|
||||
* - day-of-month: 1-28
|
||||
* - hour: 0-23
|
||||
* - min: (0-30)*2
|
||||
*
|
||||
* Giving us a total range of 0-16128000 (0xf61800), ie less
|
||||
* than 24 bits of actual data we can save across reboots.
|
||||
*
|
||||
* And if your box can't boot in less than three minutes,
|
||||
* you're screwed.
|
||||
*
|
||||
* Now, almost 24 bits of data is pitifully small, so we need
|
||||
* to be pretty dense if we want to use it for anything nice.
|
||||
* What we do is that instead of saving off nice readable info,
|
||||
* we save off _hashes_ of information that we can hopefully
|
||||
* regenerate after the reboot.
|
||||
*
|
||||
* In particular, this means that we might be unlucky, and hit
|
||||
* a case where we have a hash collision, and we end up not
|
||||
* being able to tell for certain exactly which case happened.
|
||||
* But that's hopefully unlikely.
|
||||
*
|
||||
* What we do is to take the bits we can fit, and split them
|
||||
* into three parts (16*997*1009 = 16095568), and use the values
|
||||
* for:
|
||||
* - 0-15: user-settable
|
||||
* - 0-996: file + line number
|
||||
* - 0-1008: device
|
||||
*/
|
||||
#define USERHASH (16)
|
||||
#define FILEHASH (997)
|
||||
#define DEVHASH (1009)
|
||||
|
||||
#define DEVSEED (7919)
|
||||
|
||||
static unsigned int dev_hash_value;
|
||||
|
||||
static int set_magic_time(unsigned int user, unsigned int file, unsigned int device)
|
||||
{
|
||||
unsigned int n = user + USERHASH*(file + FILEHASH*device);
|
||||
|
||||
// June 7th, 2006
|
||||
static struct rtc_time time = {
|
||||
.tm_sec = 0,
|
||||
.tm_min = 0,
|
||||
.tm_hour = 0,
|
||||
.tm_mday = 7,
|
||||
.tm_mon = 5, // June - counting from zero
|
||||
.tm_year = 106,
|
||||
.tm_wday = 3,
|
||||
.tm_yday = 160,
|
||||
.tm_isdst = 1
|
||||
};
|
||||
|
||||
time.tm_year = (n % 100);
|
||||
n /= 100;
|
||||
time.tm_mon = (n % 12);
|
||||
n /= 12;
|
||||
time.tm_mday = (n % 28) + 1;
|
||||
n /= 28;
|
||||
time.tm_hour = (n % 24);
|
||||
n /= 24;
|
||||
time.tm_min = (n % 20) * 3;
|
||||
n /= 20;
|
||||
set_rtc_time(&time);
|
||||
return n ? -1 : 0;
|
||||
}
|
||||
|
||||
static unsigned int read_magic_time(void)
|
||||
{
|
||||
struct rtc_time time;
|
||||
unsigned int val;
|
||||
|
||||
get_rtc_time(&time);
|
||||
printk("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n",
|
||||
time.tm_hour, time.tm_min, time.tm_sec,
|
||||
time.tm_mon, time.tm_mday, time.tm_year);
|
||||
val = time.tm_year; /* 100 years */
|
||||
if (val > 100)
|
||||
val -= 100;
|
||||
val += time.tm_mon * 100; /* 12 months */
|
||||
val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */
|
||||
val += time.tm_hour * 100 * 12 * 28; /* 24 hours */
|
||||
val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is just the sdbm hash function with a user-supplied
|
||||
* seed and final size parameter.
|
||||
*/
|
||||
static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod)
|
||||
{
|
||||
unsigned char c;
|
||||
while ((c = *data++) != 0) {
|
||||
seed = (seed << 16) + (seed << 6) - seed + c;
|
||||
}
|
||||
return seed % mod;
|
||||
}
|
||||
|
||||
void set_trace_device(struct device *dev)
|
||||
{
|
||||
dev_hash_value = hash_string(DEVSEED, dev->bus_id, DEVHASH);
|
||||
}
|
||||
|
||||
/*
|
||||
* We could just take the "tracedata" index into the .tracedata
|
||||
* section instead. Generating a hash of the data gives us a
|
||||
* chance to work across kernel versions, and perhaps more
|
||||
* importantly it also gives us valid/invalid check (ie we will
|
||||
* likely not give totally bogus reports - if the hash matches,
|
||||
* it's not any guarantee, but it's a high _likelihood_ that
|
||||
* the match is valid).
|
||||
*/
|
||||
void generate_resume_trace(void *tracedata, unsigned int user)
|
||||
{
|
||||
unsigned short lineno = *(unsigned short *)tracedata;
|
||||
const char *file = *(const char **)(tracedata + 2);
|
||||
unsigned int user_hash_value, file_hash_value;
|
||||
|
||||
user_hash_value = user % USERHASH;
|
||||
file_hash_value = hash_string(lineno, file, FILEHASH);
|
||||
set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
|
||||
}
|
||||
|
||||
extern char __tracedata_start, __tracedata_end;
|
||||
static int show_file_hash(unsigned int value)
|
||||
{
|
||||
int match;
|
||||
char *tracedata;
|
||||
|
||||
match = 0;
|
||||
for (tracedata = &__tracedata_start ; tracedata < &__tracedata_end ; tracedata += 6) {
|
||||
unsigned short lineno = *(unsigned short *)tracedata;
|
||||
const char *file = *(const char **)(tracedata + 2);
|
||||
unsigned int hash = hash_string(lineno, file, FILEHASH);
|
||||
if (hash != value)
|
||||
continue;
|
||||
printk(" hash matches %s:%u\n", file, lineno);
|
||||
match++;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
static int show_dev_hash(unsigned int value)
|
||||
{
|
||||
int match = 0;
|
||||
struct list_head * entry = dpm_active.prev;
|
||||
|
||||
while (entry != &dpm_active) {
|
||||
struct device * dev = to_device(entry);
|
||||
unsigned int hash = hash_string(DEVSEED, dev->bus_id, DEVHASH);
|
||||
if (hash == value) {
|
||||
printk(" hash matches device %s\n", dev->bus_id);
|
||||
match++;
|
||||
}
|
||||
entry = entry->prev;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
static unsigned int hash_value_early_read;
|
||||
|
||||
static int early_resume_init(void)
|
||||
{
|
||||
hash_value_early_read = read_magic_time();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int late_resume_init(void)
|
||||
{
|
||||
unsigned int val = hash_value_early_read;
|
||||
unsigned int user, file, dev;
|
||||
|
||||
user = val % USERHASH;
|
||||
val = val / USERHASH;
|
||||
file = val % FILEHASH;
|
||||
val = val / FILEHASH;
|
||||
dev = val /* % DEVHASH */;
|
||||
|
||||
printk(" Magic number: %d:%d:%d\n", user, file, dev);
|
||||
show_file_hash(file);
|
||||
show_dev_hash(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
core_initcall(early_resume_init);
|
||||
late_initcall(late_resume_init);
|
||||
509
drivers/base/sys.c
Normal file
509
drivers/base/sys.c
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* sys.c - pseudo-bus for system 'devices' (cpus, PICs, timers, etc)
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* 2002-3 Open Source Development Lab
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
* This exports a 'system' bus type.
|
||||
* By default, a 'sys' bus gets added to the root of the system. There will
|
||||
* always be core system devices. Devices can use sysdev_register() to
|
||||
* add themselves as children of the system bus.
|
||||
*/
|
||||
|
||||
#include <linux/sysdev.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/device.h>
|
||||
#include <asm/semaphore.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
extern struct subsystem devices_subsys;
|
||||
|
||||
#define to_sysdev(k) container_of(k, struct sys_device, kobj)
|
||||
#define to_sysdev_attr(a) container_of(a, struct sysdev_attribute, attr)
|
||||
|
||||
|
||||
static ssize_t
|
||||
sysdev_show(struct kobject * kobj, struct attribute * attr, char * buffer)
|
||||
{
|
||||
struct sys_device * sysdev = to_sysdev(kobj);
|
||||
struct sysdev_attribute * sysdev_attr = to_sysdev_attr(attr);
|
||||
|
||||
if (sysdev_attr->show)
|
||||
return sysdev_attr->show(sysdev, buffer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
sysdev_store(struct kobject * kobj, struct attribute * attr,
|
||||
const char * buffer, size_t count)
|
||||
{
|
||||
struct sys_device * sysdev = to_sysdev(kobj);
|
||||
struct sysdev_attribute * sysdev_attr = to_sysdev_attr(attr);
|
||||
|
||||
if (sysdev_attr->store)
|
||||
return sysdev_attr->store(sysdev, buffer, count);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static struct sysfs_ops sysfs_ops = {
|
||||
.show = sysdev_show,
|
||||
.store = sysdev_store,
|
||||
};
|
||||
|
||||
static struct kobj_type ktype_sysdev = {
|
||||
.sysfs_ops = &sysfs_ops,
|
||||
};
|
||||
|
||||
|
||||
int sysdev_create_file(struct sys_device * s, struct sysdev_attribute * a)
|
||||
{
|
||||
return sysfs_create_file(&s->kobj, &a->attr);
|
||||
}
|
||||
|
||||
|
||||
void sysdev_remove_file(struct sys_device * s, struct sysdev_attribute * a)
|
||||
{
|
||||
sysfs_remove_file(&s->kobj, &a->attr);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sysdev_create_file);
|
||||
EXPORT_SYMBOL_GPL(sysdev_remove_file);
|
||||
|
||||
#define to_sysdev_class(k) container_of(k, struct sysdev_class, kset.kobj)
|
||||
#define to_sysdev_class_attr(a) container_of(a, \
|
||||
struct sysdev_class_attribute, attr)
|
||||
|
||||
static ssize_t sysdev_class_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buffer)
|
||||
{
|
||||
struct sysdev_class * class = to_sysdev_class(kobj);
|
||||
struct sysdev_class_attribute *class_attr = to_sysdev_class_attr(attr);
|
||||
|
||||
if (class_attr->show)
|
||||
return class_attr->show(class, buffer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static ssize_t sysdev_class_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buffer, size_t count)
|
||||
{
|
||||
struct sysdev_class * class = to_sysdev_class(kobj);
|
||||
struct sysdev_class_attribute * class_attr = to_sysdev_class_attr(attr);
|
||||
|
||||
if (class_attr->store)
|
||||
return class_attr->store(class, buffer, count);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static struct sysfs_ops sysfs_class_ops = {
|
||||
.show = sysdev_class_show,
|
||||
.store = sysdev_class_store,
|
||||
};
|
||||
|
||||
static struct kobj_type ktype_sysdev_class = {
|
||||
.sysfs_ops = &sysfs_class_ops,
|
||||
};
|
||||
|
||||
int sysdev_class_create_file(struct sysdev_class *c,
|
||||
struct sysdev_class_attribute *a)
|
||||
{
|
||||
return sysfs_create_file(&c->kset.kobj, &a->attr);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sysdev_class_create_file);
|
||||
|
||||
void sysdev_class_remove_file(struct sysdev_class *c,
|
||||
struct sysdev_class_attribute *a)
|
||||
{
|
||||
sysfs_remove_file(&c->kset.kobj, &a->attr);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sysdev_class_remove_file);
|
||||
|
||||
/*
|
||||
* declare system_subsys
|
||||
*/
|
||||
static decl_subsys(system, &ktype_sysdev_class, NULL);
|
||||
|
||||
int sysdev_class_register(struct sysdev_class * cls)
|
||||
{
|
||||
pr_debug("Registering sysdev class '%s'\n",
|
||||
kobject_name(&cls->kset.kobj));
|
||||
INIT_LIST_HEAD(&cls->drivers);
|
||||
cls->kset.subsys = &system_subsys;
|
||||
kset_set_kset_s(cls, system_subsys);
|
||||
return kset_register(&cls->kset);
|
||||
}
|
||||
|
||||
void sysdev_class_unregister(struct sysdev_class * cls)
|
||||
{
|
||||
pr_debug("Unregistering sysdev class '%s'\n",
|
||||
kobject_name(&cls->kset.kobj));
|
||||
kset_unregister(&cls->kset);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sysdev_class_register);
|
||||
EXPORT_SYMBOL_GPL(sysdev_class_unregister);
|
||||
|
||||
|
||||
static LIST_HEAD(sysdev_drivers);
|
||||
static DECLARE_MUTEX(sysdev_drivers_lock);
|
||||
|
||||
/**
|
||||
* sysdev_driver_register - Register auxillary driver
|
||||
* @cls: Device class driver belongs to.
|
||||
* @drv: Driver.
|
||||
*
|
||||
* If @cls is valid, then @drv is inserted into @cls->drivers to be
|
||||
* called on each operation on devices of that class. The refcount
|
||||
* of @cls is incremented.
|
||||
* Otherwise, @drv is inserted into sysdev_drivers, and called for
|
||||
* each device.
|
||||
*/
|
||||
|
||||
int sysdev_driver_register(struct sysdev_class * cls,
|
||||
struct sysdev_driver * drv)
|
||||
{
|
||||
down(&sysdev_drivers_lock);
|
||||
if (cls && kset_get(&cls->kset)) {
|
||||
list_add_tail(&drv->entry, &cls->drivers);
|
||||
|
||||
/* If devices of this class already exist, tell the driver */
|
||||
if (drv->add) {
|
||||
struct sys_device *dev;
|
||||
list_for_each_entry(dev, &cls->kset.list, kobj.entry)
|
||||
drv->add(dev);
|
||||
}
|
||||
} else
|
||||
list_add_tail(&drv->entry, &sysdev_drivers);
|
||||
up(&sysdev_drivers_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sysdev_driver_unregister - Remove an auxillary driver.
|
||||
* @cls: Class driver belongs to.
|
||||
* @drv: Driver.
|
||||
*/
|
||||
void sysdev_driver_unregister(struct sysdev_class * cls,
|
||||
struct sysdev_driver * drv)
|
||||
{
|
||||
down(&sysdev_drivers_lock);
|
||||
list_del_init(&drv->entry);
|
||||
if (cls) {
|
||||
if (drv->remove) {
|
||||
struct sys_device *dev;
|
||||
list_for_each_entry(dev, &cls->kset.list, kobj.entry)
|
||||
drv->remove(dev);
|
||||
}
|
||||
kset_put(&cls->kset);
|
||||
}
|
||||
up(&sysdev_drivers_lock);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sysdev_driver_register);
|
||||
EXPORT_SYMBOL_GPL(sysdev_driver_unregister);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* sysdev_register - add a system device to the tree
|
||||
* @sysdev: device in question
|
||||
*
|
||||
*/
|
||||
int sysdev_register(struct sys_device * sysdev)
|
||||
{
|
||||
int error;
|
||||
struct sysdev_class * cls = sysdev->cls;
|
||||
|
||||
if (!cls)
|
||||
return -EINVAL;
|
||||
|
||||
/* Make sure the kset is set */
|
||||
sysdev->kobj.kset = &cls->kset;
|
||||
|
||||
/* But make sure we point to the right type for sysfs translation */
|
||||
sysdev->kobj.ktype = &ktype_sysdev;
|
||||
error = kobject_set_name(&sysdev->kobj, "%s%d",
|
||||
kobject_name(&cls->kset.kobj), sysdev->id);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
pr_debug("Registering sys device '%s'\n", kobject_name(&sysdev->kobj));
|
||||
|
||||
/* Register the object */
|
||||
error = kobject_register(&sysdev->kobj);
|
||||
|
||||
if (!error) {
|
||||
struct sysdev_driver * drv;
|
||||
|
||||
down(&sysdev_drivers_lock);
|
||||
/* Generic notification is implicit, because it's that
|
||||
* code that should have called us.
|
||||
*/
|
||||
|
||||
/* Notify global drivers */
|
||||
list_for_each_entry(drv, &sysdev_drivers, entry) {
|
||||
if (drv->add)
|
||||
drv->add(sysdev);
|
||||
}
|
||||
|
||||
/* Notify class auxillary drivers */
|
||||
list_for_each_entry(drv, &cls->drivers, entry) {
|
||||
if (drv->add)
|
||||
drv->add(sysdev);
|
||||
}
|
||||
up(&sysdev_drivers_lock);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void sysdev_unregister(struct sys_device * sysdev)
|
||||
{
|
||||
struct sysdev_driver * drv;
|
||||
|
||||
down(&sysdev_drivers_lock);
|
||||
list_for_each_entry(drv, &sysdev_drivers, entry) {
|
||||
if (drv->remove)
|
||||
drv->remove(sysdev);
|
||||
}
|
||||
|
||||
list_for_each_entry(drv, &sysdev->cls->drivers, entry) {
|
||||
if (drv->remove)
|
||||
drv->remove(sysdev);
|
||||
}
|
||||
up(&sysdev_drivers_lock);
|
||||
|
||||
kobject_unregister(&sysdev->kobj);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* sysdev_shutdown - Shut down all system devices.
|
||||
*
|
||||
* Loop over each class of system devices, and the devices in each
|
||||
* of those classes. For each device, we call the shutdown method for
|
||||
* each driver registered for the device - the globals, the auxillaries,
|
||||
* and the class driver.
|
||||
*
|
||||
* Note: The list is iterated in reverse order, so that we shut down
|
||||
* child devices before we shut down thier parents. The list ordering
|
||||
* is guaranteed by virtue of the fact that child devices are registered
|
||||
* after their parents.
|
||||
*/
|
||||
|
||||
void sysdev_shutdown(void)
|
||||
{
|
||||
struct sysdev_class * cls;
|
||||
|
||||
pr_debug("Shutting Down System Devices\n");
|
||||
|
||||
down(&sysdev_drivers_lock);
|
||||
list_for_each_entry_reverse(cls, &system_subsys.kset.list,
|
||||
kset.kobj.entry) {
|
||||
struct sys_device * sysdev;
|
||||
|
||||
pr_debug("Shutting down type '%s':\n",
|
||||
kobject_name(&cls->kset.kobj));
|
||||
|
||||
list_for_each_entry(sysdev, &cls->kset.list, kobj.entry) {
|
||||
struct sysdev_driver * drv;
|
||||
pr_debug(" %s\n", kobject_name(&sysdev->kobj));
|
||||
|
||||
/* Call global drivers first. */
|
||||
list_for_each_entry(drv, &sysdev_drivers, entry) {
|
||||
if (drv->shutdown)
|
||||
drv->shutdown(sysdev);
|
||||
}
|
||||
|
||||
/* Call auxillary drivers next. */
|
||||
list_for_each_entry(drv, &cls->drivers, entry) {
|
||||
if (drv->shutdown)
|
||||
drv->shutdown(sysdev);
|
||||
}
|
||||
|
||||
/* Now call the generic one */
|
||||
if (cls->shutdown)
|
||||
cls->shutdown(sysdev);
|
||||
}
|
||||
}
|
||||
up(&sysdev_drivers_lock);
|
||||
}
|
||||
|
||||
static void __sysdev_resume(struct sys_device *dev)
|
||||
{
|
||||
struct sysdev_class *cls = dev->cls;
|
||||
struct sysdev_driver *drv;
|
||||
|
||||
/* First, call the class-specific one */
|
||||
if (cls->resume)
|
||||
cls->resume(dev);
|
||||
|
||||
/* Call auxillary drivers next. */
|
||||
list_for_each_entry(drv, &cls->drivers, entry) {
|
||||
if (drv->resume)
|
||||
drv->resume(dev);
|
||||
}
|
||||
|
||||
/* Call global drivers. */
|
||||
list_for_each_entry(drv, &sysdev_drivers, entry) {
|
||||
if (drv->resume)
|
||||
drv->resume(dev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sysdev_suspend - Suspend all system devices.
|
||||
* @state: Power state to enter.
|
||||
*
|
||||
* We perform an almost identical operation as sys_device_shutdown()
|
||||
* above, though calling ->suspend() instead. Interrupts are disabled
|
||||
* when this called. Devices are responsible for both saving state and
|
||||
* quiescing or powering down the device.
|
||||
*
|
||||
* This is only called by the device PM core, so we let them handle
|
||||
* all synchronization.
|
||||
*/
|
||||
|
||||
int sysdev_suspend(pm_message_t state)
|
||||
{
|
||||
struct sysdev_class * cls;
|
||||
struct sys_device *sysdev, *err_dev;
|
||||
struct sysdev_driver *drv, *err_drv;
|
||||
int ret;
|
||||
|
||||
pr_debug("Suspending System Devices\n");
|
||||
|
||||
list_for_each_entry_reverse(cls, &system_subsys.kset.list,
|
||||
kset.kobj.entry) {
|
||||
|
||||
pr_debug("Suspending type '%s':\n",
|
||||
kobject_name(&cls->kset.kobj));
|
||||
|
||||
list_for_each_entry(sysdev, &cls->kset.list, kobj.entry) {
|
||||
pr_debug(" %s\n", kobject_name(&sysdev->kobj));
|
||||
|
||||
/* Call global drivers first. */
|
||||
list_for_each_entry(drv, &sysdev_drivers, entry) {
|
||||
if (drv->suspend) {
|
||||
ret = drv->suspend(sysdev, state);
|
||||
if (ret)
|
||||
goto gbl_driver;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call auxillary drivers next. */
|
||||
list_for_each_entry(drv, &cls->drivers, entry) {
|
||||
if (drv->suspend) {
|
||||
ret = drv->suspend(sysdev, state);
|
||||
if (ret)
|
||||
goto aux_driver;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now call the generic one */
|
||||
if (cls->suspend) {
|
||||
ret = cls->suspend(sysdev, state);
|
||||
if (ret)
|
||||
goto cls_driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
/* resume current sysdev */
|
||||
cls_driver:
|
||||
drv = NULL;
|
||||
printk(KERN_ERR "Class suspend failed for %s\n",
|
||||
kobject_name(&sysdev->kobj));
|
||||
|
||||
aux_driver:
|
||||
if (drv)
|
||||
printk(KERN_ERR "Class driver suspend failed for %s\n",
|
||||
kobject_name(&sysdev->kobj));
|
||||
list_for_each_entry(err_drv, &cls->drivers, entry) {
|
||||
if (err_drv == drv)
|
||||
break;
|
||||
if (err_drv->resume)
|
||||
err_drv->resume(sysdev);
|
||||
}
|
||||
drv = NULL;
|
||||
|
||||
gbl_driver:
|
||||
if (drv)
|
||||
printk(KERN_ERR "sysdev driver suspend failed for %s\n",
|
||||
kobject_name(&sysdev->kobj));
|
||||
list_for_each_entry(err_drv, &sysdev_drivers, entry) {
|
||||
if (err_drv == drv)
|
||||
break;
|
||||
if (err_drv->resume)
|
||||
err_drv->resume(sysdev);
|
||||
}
|
||||
/* resume other sysdevs in current class */
|
||||
list_for_each_entry(err_dev, &cls->kset.list, kobj.entry) {
|
||||
if (err_dev == sysdev)
|
||||
break;
|
||||
pr_debug(" %s\n", kobject_name(&err_dev->kobj));
|
||||
__sysdev_resume(err_dev);
|
||||
}
|
||||
|
||||
/* resume other classes */
|
||||
list_for_each_entry_continue(cls, &system_subsys.kset.list,
|
||||
kset.kobj.entry) {
|
||||
list_for_each_entry(err_dev, &cls->kset.list, kobj.entry) {
|
||||
pr_debug(" %s\n", kobject_name(&err_dev->kobj));
|
||||
__sysdev_resume(err_dev);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sysdev_resume - Bring system devices back to life.
|
||||
*
|
||||
* Similar to sys_device_suspend(), but we iterate the list forwards
|
||||
* to guarantee that parent devices are resumed before their children.
|
||||
*
|
||||
* Note: Interrupts are disabled when called.
|
||||
*/
|
||||
|
||||
int sysdev_resume(void)
|
||||
{
|
||||
struct sysdev_class * cls;
|
||||
|
||||
pr_debug("Resuming System Devices\n");
|
||||
|
||||
list_for_each_entry(cls, &system_subsys.kset.list, kset.kobj.entry) {
|
||||
struct sys_device * sysdev;
|
||||
|
||||
pr_debug("Resuming type '%s':\n",
|
||||
kobject_name(&cls->kset.kobj));
|
||||
|
||||
list_for_each_entry(sysdev, &cls->kset.list, kobj.entry) {
|
||||
pr_debug(" %s\n", kobject_name(&sysdev->kobj));
|
||||
|
||||
__sysdev_resume(sysdev);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int __init system_bus_init(void)
|
||||
{
|
||||
system_subsys.kset.kobj.parent = &devices_subsys.kset.kobj;
|
||||
return subsystem_register(&system_subsys);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(sysdev_register);
|
||||
EXPORT_SYMBOL_GPL(sysdev_unregister);
|
||||
154
drivers/base/topology.c
Normal file
154
drivers/base/topology.c
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* driver/base/topology.c - Populate sysfs with cpu topology information
|
||||
*
|
||||
* Written by: Zhang Yanmin, Intel Corporation
|
||||
*
|
||||
* Copyright (C) 2006, Intel Corp.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under 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, GOOD TITLE or
|
||||
* NON INFRINGEMENT. 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, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
*/
|
||||
#include <linux/sysdev.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/topology.h>
|
||||
|
||||
#define define_one_ro(_name) \
|
||||
static SYSDEV_ATTR(_name, 0444, show_##_name, NULL)
|
||||
|
||||
#define define_id_show_func(name) \
|
||||
static ssize_t show_##name(struct sys_device *dev, char *buf) \
|
||||
{ \
|
||||
unsigned int cpu = dev->id; \
|
||||
return sprintf(buf, "%d\n", topology_##name(cpu)); \
|
||||
}
|
||||
|
||||
#define define_siblings_show_func(name) \
|
||||
static ssize_t show_##name(struct sys_device *dev, char *buf) \
|
||||
{ \
|
||||
ssize_t len = -1; \
|
||||
unsigned int cpu = dev->id; \
|
||||
len = cpumask_scnprintf(buf, NR_CPUS+1, topology_##name(cpu)); \
|
||||
return (len + sprintf(buf + len, "\n")); \
|
||||
}
|
||||
|
||||
#ifdef topology_physical_package_id
|
||||
define_id_show_func(physical_package_id);
|
||||
define_one_ro(physical_package_id);
|
||||
#define ref_physical_package_id_attr &attr_physical_package_id.attr,
|
||||
#else
|
||||
#define ref_physical_package_id_attr
|
||||
#endif
|
||||
|
||||
#ifdef topology_core_id
|
||||
define_id_show_func(core_id);
|
||||
define_one_ro(core_id);
|
||||
#define ref_core_id_attr &attr_core_id.attr,
|
||||
#else
|
||||
#define ref_core_id_attr
|
||||
#endif
|
||||
|
||||
#ifdef topology_thread_siblings
|
||||
define_siblings_show_func(thread_siblings);
|
||||
define_one_ro(thread_siblings);
|
||||
#define ref_thread_siblings_attr &attr_thread_siblings.attr,
|
||||
#else
|
||||
#define ref_thread_siblings_attr
|
||||
#endif
|
||||
|
||||
#ifdef topology_core_siblings
|
||||
define_siblings_show_func(core_siblings);
|
||||
define_one_ro(core_siblings);
|
||||
#define ref_core_siblings_attr &attr_core_siblings.attr,
|
||||
#else
|
||||
#define ref_core_siblings_attr
|
||||
#endif
|
||||
|
||||
static struct attribute *default_attrs[] = {
|
||||
ref_physical_package_id_attr
|
||||
ref_core_id_attr
|
||||
ref_thread_siblings_attr
|
||||
ref_core_siblings_attr
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group topology_attr_group = {
|
||||
.attrs = default_attrs,
|
||||
.name = "topology"
|
||||
};
|
||||
|
||||
static cpumask_t topology_dev_map = CPU_MASK_NONE;
|
||||
|
||||
/* Add/Remove cpu_topology interface for CPU device */
|
||||
static int __cpuinit topology_add_dev(unsigned int cpu)
|
||||
{
|
||||
int rc;
|
||||
struct sys_device *sys_dev = get_cpu_sysdev(cpu);
|
||||
|
||||
rc = sysfs_create_group(&sys_dev->kobj, &topology_attr_group);
|
||||
if (!rc)
|
||||
cpu_set(cpu, topology_dev_map);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void __cpuinit topology_remove_dev(unsigned int cpu)
|
||||
{
|
||||
struct sys_device *sys_dev = get_cpu_sysdev(cpu);
|
||||
|
||||
if (!cpu_isset(cpu, topology_dev_map))
|
||||
return;
|
||||
cpu_clear(cpu, topology_dev_map);
|
||||
sysfs_remove_group(&sys_dev->kobj, &topology_attr_group);
|
||||
}
|
||||
|
||||
static int __cpuinit topology_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action, void *hcpu)
|
||||
{
|
||||
unsigned int cpu = (unsigned long)hcpu;
|
||||
int rc = 0;
|
||||
|
||||
switch (action) {
|
||||
case CPU_UP_PREPARE:
|
||||
rc = topology_add_dev(cpu);
|
||||
break;
|
||||
case CPU_UP_CANCELED:
|
||||
case CPU_DEAD:
|
||||
topology_remove_dev(cpu);
|
||||
break;
|
||||
}
|
||||
return rc ? NOTIFY_BAD : NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int __cpuinit topology_sysfs_init(void)
|
||||
{
|
||||
int cpu;
|
||||
int rc;
|
||||
|
||||
for_each_online_cpu(cpu) {
|
||||
rc = topology_add_dev(cpu);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
hotcpu_notifier(topology_cpu_callback, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(topology_sysfs_init);
|
||||
280
drivers/base/transport_class.c
Normal file
280
drivers/base/transport_class.c
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* transport_class.c - implementation of generic transport classes
|
||||
* using attribute_containers
|
||||
*
|
||||
* Copyright (c) 2005 - James Bottomley <James.Bottomley@steeleye.com>
|
||||
*
|
||||
* This file is licensed under GPLv2
|
||||
*
|
||||
* The basic idea here is to allow any "device controller" (which
|
||||
* would most often be a Host Bus Adapter to use the services of one
|
||||
* or more tranport classes for performing transport specific
|
||||
* services. Transport specific services are things that the generic
|
||||
* command layer doesn't want to know about (speed settings, line
|
||||
* condidtioning, etc), but which the user might be interested in.
|
||||
* Thus, the HBA's use the routines exported by the transport classes
|
||||
* to perform these functions. The transport classes export certain
|
||||
* values to the user via sysfs using attribute containers.
|
||||
*
|
||||
* Note: because not every HBA will care about every transport
|
||||
* attribute, there's a many to one relationship that goes like this:
|
||||
*
|
||||
* transport class<-----attribute container<----class device
|
||||
*
|
||||
* Usually the attribute container is per-HBA, but the design doesn't
|
||||
* mandate that. Although most of the services will be specific to
|
||||
* the actual external storage connection used by the HBA, the generic
|
||||
* transport class is framed entirely in terms of generic devices to
|
||||
* allow it to be used by any physical HBA in the system.
|
||||
*/
|
||||
#include <linux/attribute_container.h>
|
||||
#include <linux/transport_class.h>
|
||||
|
||||
/**
|
||||
* transport_class_register - register an initial transport class
|
||||
*
|
||||
* @tclass: a pointer to the transport class structure to be initialised
|
||||
*
|
||||
* The transport class contains an embedded class which is used to
|
||||
* identify it. The caller should initialise this structure with
|
||||
* zeros and then generic class must have been initialised with the
|
||||
* actual transport class unique name. There's a macro
|
||||
* DECLARE_TRANSPORT_CLASS() to do this (declared classes still must
|
||||
* be registered).
|
||||
*
|
||||
* Returns 0 on success or error on failure.
|
||||
*/
|
||||
int transport_class_register(struct transport_class *tclass)
|
||||
{
|
||||
return class_register(&tclass->class);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(transport_class_register);
|
||||
|
||||
/**
|
||||
* transport_class_unregister - unregister a previously registered class
|
||||
*
|
||||
* @tclass: The transport class to unregister
|
||||
*
|
||||
* Must be called prior to deallocating the memory for the transport
|
||||
* class.
|
||||
*/
|
||||
void transport_class_unregister(struct transport_class *tclass)
|
||||
{
|
||||
class_unregister(&tclass->class);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(transport_class_unregister);
|
||||
|
||||
static int anon_transport_dummy_function(struct transport_container *tc,
|
||||
struct device *dev,
|
||||
struct class_device *cdev)
|
||||
{
|
||||
/* do nothing */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* anon_transport_class_register - register an anonymous class
|
||||
*
|
||||
* @atc: The anon transport class to register
|
||||
*
|
||||
* The anonymous transport class contains both a transport class and a
|
||||
* container. The idea of an anonymous class is that it never
|
||||
* actually has any device attributes associated with it (and thus
|
||||
* saves on container storage). So it can only be used for triggering
|
||||
* events. Use prezero and then use DECLARE_ANON_TRANSPORT_CLASS() to
|
||||
* initialise the anon transport class storage.
|
||||
*/
|
||||
int anon_transport_class_register(struct anon_transport_class *atc)
|
||||
{
|
||||
int error;
|
||||
atc->container.class = &atc->tclass.class;
|
||||
attribute_container_set_no_classdevs(&atc->container);
|
||||
error = attribute_container_register(&atc->container);
|
||||
if (error)
|
||||
return error;
|
||||
atc->tclass.setup = anon_transport_dummy_function;
|
||||
atc->tclass.remove = anon_transport_dummy_function;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(anon_transport_class_register);
|
||||
|
||||
/**
|
||||
* anon_transport_class_unregister - unregister an anon class
|
||||
*
|
||||
* @atc: Pointer to the anon transport class to unregister
|
||||
*
|
||||
* Must be called prior to deallocating the memory for the anon
|
||||
* transport class.
|
||||
*/
|
||||
void anon_transport_class_unregister(struct anon_transport_class *atc)
|
||||
{
|
||||
attribute_container_unregister(&atc->container);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(anon_transport_class_unregister);
|
||||
|
||||
static int transport_setup_classdev(struct attribute_container *cont,
|
||||
struct device *dev,
|
||||
struct class_device *classdev)
|
||||
{
|
||||
struct transport_class *tclass = class_to_transport_class(cont->class);
|
||||
struct transport_container *tcont = attribute_container_to_transport_container(cont);
|
||||
|
||||
if (tclass->setup)
|
||||
tclass->setup(tcont, dev, classdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* transport_setup_device - declare a new dev for transport class association
|
||||
* but don't make it visible yet.
|
||||
*
|
||||
* @dev: the generic device representing the entity being added
|
||||
*
|
||||
* Usually, dev represents some component in the HBA system (either
|
||||
* the HBA itself or a device remote across the HBA bus). This
|
||||
* routine is simply a trigger point to see if any set of transport
|
||||
* classes wishes to associate with the added device. This allocates
|
||||
* storage for the class device and initialises it, but does not yet
|
||||
* add it to the system or add attributes to it (you do this with
|
||||
* transport_add_device). If you have no need for a separate setup
|
||||
* and add operations, use transport_register_device (see
|
||||
* transport_class.h).
|
||||
*/
|
||||
|
||||
void transport_setup_device(struct device *dev)
|
||||
{
|
||||
attribute_container_add_device(dev, transport_setup_classdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(transport_setup_device);
|
||||
|
||||
static int transport_add_class_device(struct attribute_container *cont,
|
||||
struct device *dev,
|
||||
struct class_device *classdev)
|
||||
{
|
||||
int error = attribute_container_add_class_device(classdev);
|
||||
struct transport_container *tcont =
|
||||
attribute_container_to_transport_container(cont);
|
||||
|
||||
if (!error && tcont->statistics)
|
||||
error = sysfs_create_group(&classdev->kobj, tcont->statistics);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* transport_add_device - declare a new dev for transport class association
|
||||
*
|
||||
* @dev: the generic device representing the entity being added
|
||||
*
|
||||
* Usually, dev represents some component in the HBA system (either
|
||||
* the HBA itself or a device remote across the HBA bus). This
|
||||
* routine is simply a trigger point used to add the device to the
|
||||
* system and register attributes for it.
|
||||
*/
|
||||
|
||||
void transport_add_device(struct device *dev)
|
||||
{
|
||||
attribute_container_device_trigger(dev, transport_add_class_device);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(transport_add_device);
|
||||
|
||||
static int transport_configure(struct attribute_container *cont,
|
||||
struct device *dev,
|
||||
struct class_device *cdev)
|
||||
{
|
||||
struct transport_class *tclass = class_to_transport_class(cont->class);
|
||||
struct transport_container *tcont = attribute_container_to_transport_container(cont);
|
||||
|
||||
if (tclass->configure)
|
||||
tclass->configure(tcont, dev, cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* transport_configure_device - configure an already set up device
|
||||
*
|
||||
* @dev: generic device representing device to be configured
|
||||
*
|
||||
* The idea of configure is simply to provide a point within the setup
|
||||
* process to allow the transport class to extract information from a
|
||||
* device after it has been setup. This is used in SCSI because we
|
||||
* have to have a setup device to begin using the HBA, but after we
|
||||
* send the initial inquiry, we use configure to extract the device
|
||||
* parameters. The device need not have been added to be configured.
|
||||
*/
|
||||
void transport_configure_device(struct device *dev)
|
||||
{
|
||||
attribute_container_device_trigger(dev, transport_configure);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(transport_configure_device);
|
||||
|
||||
static int transport_remove_classdev(struct attribute_container *cont,
|
||||
struct device *dev,
|
||||
struct class_device *classdev)
|
||||
{
|
||||
struct transport_container *tcont =
|
||||
attribute_container_to_transport_container(cont);
|
||||
struct transport_class *tclass = class_to_transport_class(cont->class);
|
||||
|
||||
if (tclass->remove)
|
||||
tclass->remove(tcont, dev, classdev);
|
||||
|
||||
if (tclass->remove != anon_transport_dummy_function) {
|
||||
if (tcont->statistics)
|
||||
sysfs_remove_group(&classdev->kobj, tcont->statistics);
|
||||
attribute_container_class_device_del(classdev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* transport_remove_device - remove the visibility of a device
|
||||
*
|
||||
* @dev: generic device to remove
|
||||
*
|
||||
* This call removes the visibility of the device (to the user from
|
||||
* sysfs), but does not destroy it. To eliminate a device entirely
|
||||
* you must also call transport_destroy_device. If you don't need to
|
||||
* do remove and destroy as separate operations, use
|
||||
* transport_unregister_device() (see transport_class.h) which will
|
||||
* perform both calls for you.
|
||||
*/
|
||||
void transport_remove_device(struct device *dev)
|
||||
{
|
||||
attribute_container_device_trigger(dev, transport_remove_classdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(transport_remove_device);
|
||||
|
||||
static void transport_destroy_classdev(struct attribute_container *cont,
|
||||
struct device *dev,
|
||||
struct class_device *classdev)
|
||||
{
|
||||
struct transport_class *tclass = class_to_transport_class(cont->class);
|
||||
|
||||
if (tclass->remove != anon_transport_dummy_function)
|
||||
class_device_put(classdev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* transport_destroy_device - destroy a removed device
|
||||
*
|
||||
* @dev: device to eliminate from the transport class.
|
||||
*
|
||||
* This call triggers the elimination of storage associated with the
|
||||
* transport classdev. Note: all it really does is relinquish a
|
||||
* reference to the classdev. The memory will not be freed until the
|
||||
* last reference goes to zero. Note also that the classdev retains a
|
||||
* reference count on dev, so dev too will remain for as long as the
|
||||
* transport class device remains around.
|
||||
*/
|
||||
void transport_destroy_device(struct device *dev)
|
||||
{
|
||||
attribute_container_remove_device(dev, transport_destroy_classdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(transport_destroy_device);
|
||||
Reference in New Issue
Block a user