Commit 78fc20f7 authored by Patrick Mochel's avatar Patrick Mochel

Introduce struct device_class

Device classes describe a type (or class) of device, like an input device
or network device, etc. This changeset defines a struct device_class that 
each subsystem is expected to implement and register with the core.

struct device_driver gains a devclass pointer which points to the class it
belongs to. When the driver is registered, it is added to the class's list
of drivers. Whenever a device is bound to that driver, it is added to the
class by calling the class's add_device callback. 

struct device gains a class_num field which is the per-class enumerated value
of the device. It is incremented each time a device is registered with the
class.

Each device class gets a driverfs directory in class/<class name> and two
subdirectories: 'devices' and 'drivers'. For each device added to the class,
a symlink is created in the devices/ directory that points to the device's
directory in the physical hierarchy. The name of the symlink is the enumerated
number the device got when it was registered with the class.

For each driver that's added to the class, a symlink is created in the class's
drivers/ directory that points to the driver's directory. The name of this 
symlink is a concatenation of <bus name>:<driver name> (to prevent namespace
conflicts of drivers with the same name on different buses).
 
parent f4e1c439
# Makefile for the Linux device tree # Makefile for the Linux device tree
obj-y := core.o sys.o interface.o power.o bus.o \ obj-y := core.o sys.o interface.o power.o bus.o \
driver.o driver.o class.o
obj-y += fs/ obj-y += fs/
export-objs := core.o power.o sys.o bus.o driver.o export-objs := core.o power.o sys.o bus.o driver.o class.o
include $(TOPDIR)/Rules.make include $(TOPDIR)/Rules.make
...@@ -26,5 +26,14 @@ extern void driver_remove_dir(struct device_driver * drv); ...@@ -26,5 +26,14 @@ extern void driver_remove_dir(struct device_driver * drv);
extern int device_bus_link(struct device * dev); extern int device_bus_link(struct device * dev);
extern void device_remove_symlink(struct driver_dir_entry * dir, const char * name); extern void device_remove_symlink(struct driver_dir_entry * dir, const char * name);
extern int devclass_make_dir(struct device_class *);
extern void devclass_remove_dir(struct device_class *);
extern int devclass_drv_link(struct device_driver *);
extern void devclass_drv_unlink(struct device_driver *);
extern int devclass_dev_link(struct device_class *, struct device *);
extern void devclass_dev_unlink(struct device_class *, struct device *);
extern int driver_attach(struct device_driver * drv); extern int driver_attach(struct device_driver * drv);
extern void driver_detach(struct device_driver * drv); extern void driver_detach(struct device_driver * drv);
/*
* class.c - basic device class management
*/
#include <linux/device.h>
#include <linux/module.h>
#include "base.h"
static LIST_HEAD(class_list);
int devclass_add_driver(struct device_driver * drv)
{
if (drv->devclass) {
pr_debug("Registering driver %s:%s with class %s\n",
drv->bus->name,drv->name,drv->devclass->name);
spin_lock(&device_lock);
list_add_tail(&drv->class_list,&drv->devclass->drivers);
spin_unlock(&device_lock);
devclass_drv_link(drv);
}
return 0;
}
void devclass_remove_driver(struct device_driver * drv)
{
if (drv->devclass) {
pr_debug("Removing driver %s:%s:%s\n",
drv->devclass->name,drv->bus->name,drv->name);
spin_lock(&device_lock);
list_del_init(&drv->class_list);
spin_unlock(&device_lock);
devclass_drv_unlink(drv);
}
}
static void enum_device(struct device_class * cls, struct device * dev)
{
u32 val;
spin_lock(&device_lock);
val = cls->devnum++;
write_unlock(&device_lock);
dev->class_num = val;
devclass_dev_link(cls,dev);
}
static void unenum_device(struct device_class * cls, struct device * dev)
{
devclass_dev_unlink(cls,dev);
dev->class_num = 0;
}
int devclass_add_device(struct device * dev)
{
struct device_class * cls = dev->driver->devclass;
int error = 0;
if (cls) {
pr_debug("adding device '%s' to class '%s'\n",
dev->name,cls->name);
if (cls->add_device)
error = cls->add_device(dev);
if (!error)
enum_device(cls,dev);
}
return error;
}
void devclass_remove_device(struct device * dev)
{
struct device_class * cls = dev->driver->devclass;
if (cls) {
pr_debug("removing device '%s' from class '%s'\n",
dev->name,cls->name);
unenum_device(cls,dev);
if (cls->remove_device)
cls->remove_device(dev);
}
}
int devclass_register(struct device_class * cls)
{
INIT_LIST_HEAD(&cls->drivers);
pr_debug("registering device class '%s'\n",cls->name);
spin_lock(&device_lock);
list_add_tail(&cls->node,&class_list);
spin_unlock(&device_lock);
devclass_make_dir(cls);
return 0;
}
void devclass_unregister(struct device_class * cls)
{
pr_debug("unregistering device class '%s'\n",cls->name);
devclass_remove_dir(cls);
spin_lock(&device_lock);
list_del_init(&class_list);
spin_unlock(&device_lock);
}
EXPORT_SYMBOL(devclass_register);
EXPORT_SYMBOL(devclass_unregister);
obj-y := device.o bus.o driver.o obj-y := device.o bus.o driver.o class.o
export-objs := device.o bus.o driver.o export-objs := device.o bus.o driver.o class.o
include $(TOPDIR)/Rules.make include $(TOPDIR)/Rules.make
/*
* class.c - driverfs bindings for device classes.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/err.h>
#include "fs.h"
static struct driver_dir_entry class_dir;
#define to_class_attr(_attr) container_of(_attr,struct devclass_attribute,attr)
#define to_class(d) container_of(d,struct device_class,dir)
static ssize_t
devclass_attr_show(struct driver_dir_entry * dir, struct attribute * attr,
char * buf, size_t count, loff_t off)
{
struct devclass_attribute * class_attr = to_class_attr(attr);
struct device_class * dc = to_class(dir);
ssize_t ret = 0;
if (class_attr->show)
ret = class_attr->show(dc,buf,count,off);
return ret;
}
static ssize_t
devclass_attr_store(struct driver_dir_entry * dir, struct attribute * attr,
const char * buf, size_t count, loff_t off)
{
struct devclass_attribute * class_attr = to_class_attr(attr);
struct device_class * dc = to_class(dir);
ssize_t ret = 0;
if (class_attr->store)
ret = class_attr->store(dc,buf,count,off);
return ret;
}
static struct driverfs_ops devclass_attr_ops = {
show: devclass_attr_show,
store: devclass_attr_store,
};
int devclass_create_file(struct device_class * dc, struct devclass_attribute * attr)
{
int error;
if (dc) {
error = driverfs_create_file(&attr->attr,&dc->dir);
} else
error = -EINVAL;
return error;
}
void devclass_remove_file(struct device_class * dc, struct devclass_attribute * attr)
{
if (dc)
driverfs_remove_file(&dc->dir,attr->attr.name);
}
/**
* devclass_dev_link - create symlink to device's directory
* @cls - device class we're a part of
* @dev - device we're linking to
*
* Create a symlink in the class's devices/ directory to @dev's
* directory in the physical hierarchy. The name is the device's
* class-enumerated value (struct device::class_num). We're
* creating:
* class/<class name>/devices/<link name> ->
* root/<path to device>/<device's dir>
* So, the link looks like:
* ../../../root/<path to device>/
*/
int devclass_dev_link(struct device_class * cls, struct device * dev)
{
char linkname[16];
char * path;
int length;
int error;
length = get_devpath_length(dev);
length += strlen("../../../root");
if (length > PATH_MAX)
return -ENAMETOOLONG;
if (!(path = kmalloc(length,GFP_KERNEL)))
return -ENOMEM;
memset(path,0,length);
strcpy(path,"../../../root");
fill_devpath(dev,path,length);
snprintf(linkname,16,"%u",dev->class_num);
error = driverfs_create_symlink(&cls->device_dir,linkname,path);
kfree(path);
return error;
}
void devclass_dev_unlink(struct device_class * cls, struct device * dev)
{
char linkname[16];
snprintf(linkname,16,"%u",dev->class_num);
driverfs_remove_file(&cls->device_dir,linkname);
}
/**
* devclass_drv_link - create symlink to driver's directory
* @drv: driver we're linking up
*
* Create a symlink in the class's drivers/ directory to @drv's
* directory (in the bus's directory). It's name is <bus>:<driver>
* to prevent naming conflicts.
*
* We're creating
* class/<class name>/drivers/<link name> ->
* bus/<bus name>/drivers/<driver name>/
* So, the link looks like:
* ../../../bus/<bus name>/drivers/<driver name>
*/
int devclass_drv_link(struct device_driver * drv)
{
char * name;
char * path;
int namelen;
int pathlen;
int error = 0;
namelen = strlen(drv->name) + strlen(drv->bus->name) + 2;
name = kmalloc(namelen,GFP_KERNEL);
if (!name)
return -ENOMEM;
snprintf(name,namelen,"%s:%s",drv->bus->name,drv->name);
pathlen = strlen("../../../bus/") +
strlen(drv->bus->name) +
strlen("/drivers/") +
strlen(drv->name) + 1;
if (!(path = kmalloc(pathlen,GFP_KERNEL))) {
error = -ENOMEM;
goto Done;
}
snprintf(path,pathlen,"%s%s%s%s",
"../../../bus/",
drv->bus->name,
"/drivers/",
drv->name);
error = driverfs_create_symlink(&drv->devclass->driver_dir,name,path);
Done:
kfree(name);
kfree(path);
return error;
}
void devclass_drv_unlink(struct device_driver * drv)
{
char * name;
int length;
length = strlen(drv->name) + strlen(drv->bus->name) + 2;
if ((name = kmalloc(length,GFP_KERNEL))) {
driverfs_remove_file(&drv->devclass->driver_dir,name);
kfree(name);
}
}
void devclass_remove_dir(struct device_class * dc)
{
driverfs_remove_dir(&dc->device_dir);
driverfs_remove_dir(&dc->driver_dir);
driverfs_remove_dir(&dc->dir);
}
int devclass_make_dir(struct device_class * dc)
{
int error;
dc->dir.name = dc->name;
dc->dir.ops = &devclass_attr_ops;
error = device_create_dir(&dc->dir,&class_dir);
if (!error) {
dc->driver_dir.name = "drivers";
error = device_create_dir(&dc->driver_dir,&dc->dir);
if (!error) {
dc->device_dir.name = "devices";
error = device_create_dir(&dc->device_dir,&dc->dir);
}
if (error)
driverfs_remove_dir(&dc->dir);
}
return error;
}
static struct driver_dir_entry class_dir = {
name: "class",
mode: (S_IRWXU | S_IRUGO | S_IXUGO),
};
static int __init devclass_driverfs_init(void)
{
return driverfs_create_dir(&class_dir,NULL);
}
core_initcall(devclass_driverfs_init);
EXPORT_SYMBOL(devclass_create_file);
EXPORT_SYMBOL(devclass_remove_file);
...@@ -123,7 +123,7 @@ void device_remove_dir(struct device * dev) ...@@ -123,7 +123,7 @@ void device_remove_dir(struct device * dev)
driverfs_remove_dir(&dev->dir); driverfs_remove_dir(&dev->dir);
} }
static int get_devpath_length(struct device * dev) int get_devpath_length(struct device * dev)
{ {
int length = 1; int length = 1;
struct device * parent = dev; struct device * parent = dev;
...@@ -138,7 +138,7 @@ static int get_devpath_length(struct device * dev) ...@@ -138,7 +138,7 @@ static int get_devpath_length(struct device * dev)
return length; return length;
} }
static void fill_devpath(struct device * dev, char * path, int length) void fill_devpath(struct device * dev, char * path, int length)
{ {
struct device * parent; struct device * parent;
--length; --length;
......
extern int device_create_dir(struct driver_dir_entry * dir, struct driver_dir_entry * parent); extern int device_create_dir(struct driver_dir_entry * dir, struct driver_dir_entry * parent);
int get_devpath_length(struct device * dev);
void fill_devpath(struct device * dev, char * path, int length);
...@@ -50,6 +50,7 @@ enum { ...@@ -50,6 +50,7 @@ enum {
struct device; struct device;
struct device_driver; struct device_driver;
struct device_class;
struct bus_type { struct bus_type {
char * name; char * name;
...@@ -106,12 +107,14 @@ extern void bus_remove_file(struct bus_type *, struct bus_attribute *); ...@@ -106,12 +107,14 @@ extern void bus_remove_file(struct bus_type *, struct bus_attribute *);
struct device_driver { struct device_driver {
char * name; char * name;
struct bus_type * bus; struct bus_type * bus;
struct device_class * devclass;
rwlock_t lock; rwlock_t lock;
atomic_t refcount; atomic_t refcount;
list_t bus_list; struct list_head bus_list;
list_t devices; struct list_head class_list;
struct list_head devices;
struct driver_dir_entry dir; struct driver_dir_entry dir;
...@@ -160,6 +163,47 @@ struct driver_attribute driver_attr_##_name = { \ ...@@ -160,6 +163,47 @@ struct driver_attribute driver_attr_##_name = { \
extern int driver_create_file(struct device_driver *, struct driver_attribute *); extern int driver_create_file(struct device_driver *, struct driver_attribute *);
extern void driver_remove_file(struct device_driver *, struct driver_attribute *); extern void driver_remove_file(struct device_driver *, struct driver_attribute *);
/*
* device classes
*/
struct device_class {
char * name;
u32 devnum;
struct list_head node;
struct list_head drivers;
struct driver_dir_entry dir;
struct driver_dir_entry driver_dir;
struct driver_dir_entry device_dir;
int (*add_device)(struct device *);
void (*remove_device)(struct device *);
};
extern int devclass_register(struct device_class *);
extern void devclass_unregister(struct device_class *);
struct devclass_attribute {
struct attribute attr;
ssize_t (*show)(struct device_class *, char * buf, size_t count, loff_t off);
ssize_t (*store)(struct device_class *, const char * buf, size_t count, loff_t off);
};
#define DEVCLASS_ATTR(_name,_str,_mode,_show,_store) \
struct devclass_attribute devclass_attr_##_name = { \
.attr = {.name = _str, .mode = _mode }, \
.show = _show, \
.store = _store, \
};
extern int devclass_create_file(struct device_class *, struct devclass_attribute *);
extern void devclass_remove_file(struct device_class *, struct devclass_attribute *);
struct device { struct device {
struct list_head g_list; /* node in depth-first order list */ struct list_head g_list; /* node in depth-first order list */
struct list_head node; /* node in sibling list */ struct list_head node; /* node in sibling list */
...@@ -183,6 +227,8 @@ struct device { ...@@ -183,6 +227,8 @@ struct device {
struct device_driver *driver; /* which driver has allocated this struct device_driver *driver; /* which driver has allocated this
device */ device */
void *driver_data; /* data private to the driver */ void *driver_data; /* data private to the driver */
u32 class_num; /* class-enumerated value */
void *platform_data; /* Platform specific data (e.g. ACPI, void *platform_data; /* Platform specific data (e.g. ACPI,
BIOS data relevant to device) */ BIOS data relevant to device) */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment