summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Mochel <mochel@osdl.org>2003-01-10 02:57:21 -0600
committerPatrick Mochel <mochel@osdl.org>2003-01-10 02:57:21 -0600
commit601c09192dae1da98e8877e707d7ee58ac243d9b (patch)
tree32dea12f7b9a57ebe9d17f50036a72a214845b46
parentefa0596f2d555b4a544939c96814cff14b93fd9c (diff)
parentb2aa16e83d30e24f5e7faea1aa532e3f6ebdec18 (diff)
Merge osdl.org:/home/mochel/src/kernel/devel/linux-2.5-virgin
into osdl.org:/home/mochel/src/kernel/devel/linux-2.5-core
-rw-r--r--Documentation/driver-model/porting.txt445
-rw-r--r--arch/i386/kernel/edd.c55
-rw-r--r--drivers/base/bus.c55
-rw-r--r--drivers/base/class.c22
-rw-r--r--drivers/base/core.c10
-rw-r--r--drivers/base/driver.c2
-rw-r--r--drivers/base/interface.c15
-rw-r--r--drivers/base/intf.c5
-rw-r--r--drivers/block/genhd.c26
-rw-r--r--drivers/pci/Kconfig1
-rw-r--r--drivers/pci/pool.c9
-rw-r--r--drivers/pci/proc.c21
-rw-r--r--drivers/pnp/interface.c22
-rw-r--r--drivers/scsi/scsi_sysfs.c29
-rw-r--r--drivers/scsi/sg.c12
-rw-r--r--drivers/usb/core/driverfs.c31
-rw-r--r--fs/partitions/check.c32
-rw-r--r--fs/sysfs/inode.c297
-rw-r--r--include/linux/device.h16
-rw-r--r--include/linux/kobject.h4
-rw-r--r--include/linux/sysfs.h4
-rw-r--r--lib/kobject.c40
-rw-r--r--net/core/dev.c13
23 files changed, 855 insertions, 311 deletions
diff --git a/Documentation/driver-model/porting.txt b/Documentation/driver-model/porting.txt
new file mode 100644
index 000000000000..fef1c44e330a
--- /dev/null
+++ b/Documentation/driver-model/porting.txt
@@ -0,0 +1,445 @@
+
+Porting Drivers to the New Driver Model
+
+Patrick Mochel
+
+7 January 2003
+
+
+Overview
+
+Please refer to Documentation/driver-model/*.txt for definitions of
+various driver types and concepts.
+
+Most of the work of porting devices drivers to the new model happens
+at the bus driver layer. This was intentional, to minimize the
+negative effect on kernel drivers, and to allow a gradual transition
+of bus drivers.
+
+In a nutshell, the driver model consists of a set of objects that can
+be embedded in larger, bus-specific objects. Fields in these generic
+objects can replace fields in the bus-specific objects.
+
+The generic objects must be registered with the driver model core. By
+doing so, they will exported via the sysfs filesystem. sysfs can be
+mounted by doing
+
+ # mount -t sysfs sysfs /sys
+
+
+
+The Process
+
+Step 0: Read include/linux/device.h for object and function definitions.
+
+Step 1: Registering the bus driver.
+
+
+- Define a struct bus_type for the bus driver.
+
+struct bus_type pci_bus_type = {
+ .name = "pci",
+};
+
+
+- Register the bus type.
+ This should be done in the initialization function for the bus type,
+ which is usually the module_init(), or equivalent, function.
+
+static int __init pci_driver_init(void)
+{
+ return bus_register(&pci_bus_type);
+}
+
+subsys_initcall(pci_driver_init);
+
+
+ The bus type may be unregistered (if the bus driver may be compiled
+ as a module) by doing:
+
+ bus_unregister(&pci_bus_type);
+
+
+- Export the bus type for others to use.
+
+ Other code may wish to reference the bus type, so declare it in a
+ shared header file and export the symbol.
+
+From include/linux/pci.h:
+
+extern struct bus_type pci_bus_type;
+
+
+From file the above code appears in:
+
+EXPORT_SYMBOL(pci_bus_type);
+
+
+
+- This will cause the bus to show up in /sys/bus/pci/ with two
+ subdirectories: 'devices' and 'drivers'.
+
+# tree -d /sys/bus/pci/
+/sys/bus/pci/
+|-- devices
+`-- drivers
+
+
+
+Step 2: Registering Devices.
+
+struct device represents a single device. It mainly contains metadata
+describing the relationship the device has to other entities.
+
+
+- Embedd a struct device in the bus-specific device type.
+
+
+struct pci_dev {
+ ...
+ struct device dev; /* Generic device interface */
+ ...
+};
+
+ It is recommended that the generic device not be the first item in
+ the struct to discourage programmers from doing mindless casts
+ between the object types. Instead macros, or inline functions,
+ should be created to convert from the generic object type.
+
+
+#define to_pci_dev(n) container_of(n, struct pci_dev, dev)
+
+or
+
+static inline struct pci_dev * to_pci_dev(struct kobject * kobj)
+{
+ return container_of(n, struct pci_dev, dev);
+}
+
+ This allows the compiler to verify type-safety of the operations
+ that are performed (which is Good).
+
+
+- Initialize the device on registration.
+
+ When devices are discovered or registered with the bus type, the
+ bus driver should initialize the generic device. The most important
+ things to initialize are the bus_id, parent, and bus fields.
+
+ The bus_id is an ASCII string that contains the device's address on
+ the bus. The format of this string is bus-specific. This is
+ necessary for representing device in sysfs.
+
+ parent is the physical parent of the device. It is important that
+ the bus driver sets this field correctly.
+
+ The driver model maintains an ordered list of devices that it uses
+ for power management. This list must be in order to guarantee that
+ devices are shutdown before their physical parents, and vice versa.
+ The order of this list is determined by the parent of registered
+ devices.
+
+ Also, the location of the device's sysfs directory depends on a
+ device's parent. sysfs exports a directory structure that mirrors
+ the device hierarchy. Accurately setting the parent guarantees that
+ sysfs will accurately represent the hierarchy.
+
+ The device's bus field is a pointer to the bus type the device
+ belongs to. This should be set to the bus_type that was declared
+ and initialized before.
+
+ Optionally, the bus driver may set the device's name and release
+ fields.
+
+ The name field is an ASCII string describing the device, like
+
+ "ATI Technologies Inc Radeon QD"
+
+ The release field is a callback that the driver model core calls
+ when the device has been removed, and all references to it have
+ been released. More on this in a moment.
+
+
+- Register the device.
+
+ Once the generic device has been initialized, it can be registered
+ with the driver model core by doing:
+
+ device_register(&dev->dev);
+
+ It can later be unregistered by doing:
+
+ device_unregister(&dev->dev);
+
+ This should happen on buses that support hotpluggable devices.
+ If a bus driver unregisters a device, it should not immediately free
+ it. It should instead wait for the driver model core to call the
+ device's release method, then free the bus-specific object.
+ (There may be other code that is currently referencing the device
+ structure, and it would be rude to free the device while that is
+ happening).
+
+
+ When the device is registered, a directory in sysfs is created.
+ The PCI tree in sysfs looks like:
+
+/sys/devices/pci0/
+|-- 00:00.0
+|-- 00:01.0
+| `-- 01:00.0
+|-- 00:02.0
+| `-- 02:1f.0
+| `-- 03:00.0
+|-- 00:1e.0
+| `-- 04:04.0
+|-- 00:1f.0
+|-- 00:1f.1
+| |-- ide0
+| | |-- 0.0
+| | `-- 0.1
+| `-- ide1
+| `-- 1.0
+|-- 00:1f.2
+|-- 00:1f.3
+`-- 00:1f.5
+
+ Also, symlinks are created in the bus's 'devices' directory
+ that point to the device's directory in the physical hierarchy.
+
+/sys/bus/pci/devices/
+|-- 00:00.0 -> ../../../devices/pci0/00:00.0
+|-- 00:01.0 -> ../../../devices/pci0/00:01.0
+|-- 00:02.0 -> ../../../devices/pci0/00:02.0
+|-- 00:1e.0 -> ../../../devices/pci0/00:1e.0
+|-- 00:1f.0 -> ../../../devices/pci0/00:1f.0
+|-- 00:1f.1 -> ../../../devices/pci0/00:1f.1
+|-- 00:1f.2 -> ../../../devices/pci0/00:1f.2
+|-- 00:1f.3 -> ../../../devices/pci0/00:1f.3
+|-- 00:1f.5 -> ../../../devices/pci0/00:1f.5
+|-- 01:00.0 -> ../../../devices/pci0/00:01.0/01:00.0
+|-- 02:1f.0 -> ../../../devices/pci0/00:02.0/02:1f.0
+|-- 03:00.0 -> ../../../devices/pci0/00:02.0/02:1f.0/03:00.0
+`-- 04:04.0 -> ../../../devices/pci0/00:1e.0/04:04.0
+
+
+
+Step 3: Registering Drivers.
+
+struct device_driver is a simple driver structure that contains a set
+of operations that the driver model core may call.
+
+
+- Embed a struct device_driver in the bus-specific driver.
+
+ Just like with devices, do something like:
+
+struct pci_driver {
+ ...
+ struct device_driver driver;
+};
+
+
+- Initialize the generic driver structure.
+
+ When the driver registers with the bus (e.g. doing pci_register_driver()),
+ initialize the necessary fields of the driver: the name and bus
+ fields.
+
+
+- Register the driver.
+
+ After the generic driver has been initialized, call
+
+ driver_register(&drv->driver);
+
+ to register the driver with the core.
+
+ When the driver is unregistered from the bus, unregister it from the
+ core by doing:
+
+ driver_unregister(&drv->driver);
+
+ Note that this will block until all references to the driver have
+ gone away. Normally, there will not be any.
+
+
+- Sysfs representation.
+
+ Drivers are exported via sysfs in their bus's 'driver's directory.
+ For example:
+
+/sys/bus/pci/drivers/
+|-- 3c59x
+|-- Ensoniq AudioPCI
+|-- agpgart-amdk7
+|-- e100
+`-- serial
+
+
+Step 4: Define Generic Methods for Drivers.
+
+struct device_driver defines a set of operations that the driver model
+core calls. Most of these operations are probably similar to
+operations the bus already defines for drivers, but taking different
+parameters.
+
+It would be difficult and tedious to force every driver on a bus to
+simultaneously convert their drivers to generic format. Instead, the
+bus driver should define single instances of the generic methods that
+forward calls to the bus-specific drivers. For instance:
+
+
+static int pci_device_remove(struct device * dev)
+{
+ struct pci_dev * pci_dev = to_pci_dev(dev);
+ struct pci_driver * drv = pci_dev->driver;
+
+ if (drv) {
+ if (drv->remove)
+ drv->remove(pci_dev);
+ pci_dev->driver = NULL;
+ }
+ return 0;
+}
+
+
+The generic driver should be initialized with these methods before it
+is registered.
+
+ /* initialize common driver fields */
+ drv->driver.name = drv->name;
+ drv->driver.bus = &pci_bus_type;
+ drv->driver.probe = pci_device_probe;
+ drv->driver.resume = pci_device_resume;
+ drv->driver.suspend = pci_device_suspend;
+ drv->driver.remove = pci_device_remove;
+
+ /* register with core */
+ driver_register(&drv->driver);
+
+
+Ideally, the bus should only initialize the fields if they are not
+already set. This allows the drivers to implement their own generic
+methods.
+
+
+Step 5: Support generic driver binding.
+
+The model assumes that a device or driver can be dynamically
+registered with the bus at any time. When registration happens,
+devices must be bound to a driver, or drivers must be bound to all
+devices that it supports.
+
+Drivers typically contain a list of device IDs that it supports. The
+bus driver compares this ID to the ID of devices registered with it.
+The format of the device IDs, and the semantics for comparing them are
+bus-specific, so the generic model does attempt to generalize them.
+
+Instead, a bus may supply a method in struct bus_type that does the
+comparison:
+
+ int (*match)(struct device * dev, struct device_driver * drv);
+
+match should return '1' if the driver supports the device, and '0'
+otherwise.
+
+When a device is registered, the bus's list of drivers is iterated
+over. bus->match() is called for each one until a match is found.
+
+When a driver is registered, the bus's list of devices is iterated
+over. bus->match() is called for each device that is not already
+claimed by a driver.
+
+When a device is successfully bound to a device, device->driver is
+set, the device is added to a per-driver list of devices, and a
+symlink is created in the driver's sysfs directory that points to the
+device's physical directory:
+
+/sys/bus/pci/drivers/
+|-- 3c59x
+| `-- 00:0b.0 -> ../../../../devices/pci0/00:0b.0
+|-- Ensoniq AudioPCI
+|-- agpgart-amdk7
+| `-- 00:00.0 -> ../../../../devices/pci0/00:00.0
+|-- e100
+| `-- 00:0c.0 -> ../../../../devices/pci0/00:0c.0
+`-- serial
+
+
+This driver binding should replace the existing driver binding
+mechanism the bus currently uses.
+
+
+Step 6: Supply a hotplug callback.
+
+Whenever a device is registered with the driver model core, the
+userspace program /sbin/hotplug is called to notify userspace.
+Users can define actions to perform when a device is inserted or
+removed.
+
+The driver model core passes several arguments to userspace via
+environment variables, including
+
+- ACTION: set to 'add' or 'remove'
+- DEVPATH: set to the device's physical path in sysfs.
+
+A bus driver may also supply additional parameters for userspace to
+consume. To do this, a bus must implement the 'hotplug' method in
+struct bus_type:
+
+ int (*hotplug) (struct device *dev, char **envp,
+ int num_envp, char *buffer, int buffer_size);
+
+This is called immediately before /sbin/hotplug is executed.
+
+
+Step 7: Cleaning up the bus driver.
+
+The generic bus, device, and driver structures provide several fields
+that can replace those define privately to the bus driver.
+
+- Device list.
+
+struct bus_type contains a list of all devices registered with the bus
+type. This includes all devices on all instances of that bus type.
+An internal list that the bus uses may be removed, in favor of using
+this one.
+
+The core provides an iterator to access these devices.
+
+int bus_for_each_dev(struct bus_type * bus, struct device * start,
+ void * data, int (*fn)(struct device *, void *));
+
+
+- Driver list.
+
+struct bus_type also contains a list of all drivers registered with
+it. An internal list of drivers that the bus driver maintains may
+be removed in favor of using the generic one.
+
+The drivers may be iterated over, like devices:
+
+int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
+ void * data, int (*fn)(struct device_driver *, void *));
+
+
+Please see drivers/base/bus.c for more information.
+
+
+- rwsem
+
+struct bus_type contains an rwsem that protects all core accesses to
+the device and driver lists. This can be used by the bus driver
+internally, and should be used when accessing the device or driver
+lists the bus maintains.
+
+
+- Device and driver fields.
+
+Some of the fields in struct device and struct device_driver duplicate
+fields in the bus-specific representations of these objects. Feel free
+to remove the bus-specific ones and favor the generic ones. Note
+though, that this will likely mean fixing up all the drivers that
+reference the bus-specific fields (though those should all be 1-line
+changes).
+
diff --git a/arch/i386/kernel/edd.c b/arch/i386/kernel/edd.c
index 132bca8765bb..8cb1e21b56bd 100644
--- a/arch/i386/kernel/edd.c
+++ b/arch/i386/kernel/edd.c
@@ -61,7 +61,7 @@ MODULE_LICENSE("GPL");
#define EDD_DEVICE_NAME_SIZE 16
#define REPORT_URL "http://domsch.com/linux/edd30/results.html"
-#define left (count - (p - buf) - 1)
+#define left (PAGE_SIZE - (p - buf) - 1)
struct edd_device {
struct edd_info *info;
@@ -70,8 +70,7 @@ struct edd_device {
struct edd_attribute {
struct attribute attr;
- ssize_t(*show) (struct edd_device * edev, char *buf, size_t count,
- loff_t off);
+ ssize_t(*show) (struct edd_device * edev, char *buf);
int (*test) (struct edd_device * edev);
};
@@ -104,15 +103,14 @@ edd_dev_set_info(struct edd_device *edev, struct edd_info *info)
#define to_edd_device(obj) container_of(obj,struct edd_device,kobj)
static ssize_t
-edd_attr_show(struct kobject * kobj, struct attribute *attr,
- char *buf, size_t count, loff_t off)
+edd_attr_show(struct kobject * kobj, struct attribute *attr, char *buf)
{
struct edd_device *dev = to_edd_device(kobj);
struct edd_attribute *edd_attr = to_edd_attr(attr);
ssize_t ret = 0;
if (edd_attr->show)
- ret = edd_attr->show(dev, buf, count, off);
+ ret = edd_attr->show(dev, buf);
return ret;
}
@@ -156,13 +154,13 @@ edd_dump_raw_data(char *b, int count, void *data, int length)
}
static ssize_t
-edd_show_host_bus(struct edd_device *edev, char *buf, size_t count, loff_t off)
+edd_show_host_bus(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
int i;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -200,13 +198,13 @@ edd_show_host_bus(struct edd_device *edev, char *buf, size_t count, loff_t off)
}
static ssize_t
-edd_show_interface(struct edd_device *edev, char *buf, size_t count, loff_t off)
+edd_show_interface(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
int i;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -262,7 +260,7 @@ edd_show_interface(struct edd_device *edev, char *buf, size_t count, loff_t off)
* Returns: number of bytes written, or 0 on failure
*/
static ssize_t
-edd_show_raw_data(struct edd_device *edev, char *buf, size_t count, loff_t off)
+edd_show_raw_data(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
int i, rc, warn_padding = 0, email = 0, nonzero_path = 0,
@@ -271,7 +269,7 @@ edd_show_raw_data(struct edd_device *edev, char *buf, size_t count, loff_t off)
char *p = buf;
struct pci_dev *pci_dev=NULL;
struct scsi_device *sd;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -372,11 +370,11 @@ out:
}
static ssize_t
-edd_show_version(struct edd_device *edev, char *buf, size_t count, loff_t off)
+edd_show_version(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -385,12 +383,11 @@ edd_show_version(struct edd_device *edev, char *buf, size_t count, loff_t off)
}
static ssize_t
-edd_show_extensions(struct edd_device *edev, char *buf, size_t count,
- loff_t off)
+edd_show_extensions(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -410,12 +407,11 @@ edd_show_extensions(struct edd_device *edev, char *buf, size_t count,
}
static ssize_t
-edd_show_info_flags(struct edd_device *edev, char *buf, size_t count,
- loff_t off)
+edd_show_info_flags(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -439,12 +435,11 @@ edd_show_info_flags(struct edd_device *edev, char *buf, size_t count,
}
static ssize_t
-edd_show_default_cylinders(struct edd_device *edev, char *buf, size_t count,
- loff_t off)
+edd_show_default_cylinders(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -453,12 +448,11 @@ edd_show_default_cylinders(struct edd_device *edev, char *buf, size_t count,
}
static ssize_t
-edd_show_default_heads(struct edd_device *edev, char *buf, size_t count,
- loff_t off)
+edd_show_default_heads(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -467,12 +461,11 @@ edd_show_default_heads(struct edd_device *edev, char *buf, size_t count,
}
static ssize_t
-edd_show_default_sectors_per_track(struct edd_device *edev, char *buf,
- size_t count, loff_t off)
+edd_show_default_sectors_per_track(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
@@ -481,11 +474,11 @@ edd_show_default_sectors_per_track(struct edd_device *edev, char *buf,
}
static ssize_t
-edd_show_sectors(struct edd_device *edev, char *buf, size_t count, loff_t off)
+edd_show_sectors(struct edd_device *edev, char *buf)
{
struct edd_info *info = edd_dev_get_info(edev);
char *p = buf;
- if (!edev || !info || !buf || off) {
+ if (!edev || !info || !buf) {
return 0;
}
diff --git a/drivers/base/bus.c b/drivers/base/bus.c
index 782ff309b887..e682965bb062 100644
--- a/drivers/base/bus.c
+++ b/drivers/base/bus.c
@@ -31,28 +31,26 @@
static ssize_t
-drv_attr_show(struct kobject * kobj, struct attribute * attr,
- char * buf, size_t count, loff_t off)
+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 = 0;
if (drv_attr->show)
- ret = drv_attr->show(drv,buf,count,off);
+ 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, loff_t off)
+drv_attr_store(struct kobject * kobj, struct attribute * attr, const char * buf)
{
struct driver_attribute * drv_attr = to_drv_attr(attr);
struct device_driver * drv = to_driver(kobj);
ssize_t ret = 0;
if (drv_attr->store)
- ret = drv_attr->store(drv,buf,count,off);
+ ret = drv_attr->store(drv,buf);
return ret;
}
@@ -80,28 +78,26 @@ static struct kobj_type ktype_driver = {
static ssize_t
-bus_attr_show(struct kobject * kobj, struct attribute * attr,
- char * buf, size_t count, loff_t off)
+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,count,off);
+ 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, loff_t off)
+bus_attr_store(struct kobject * kobj, struct attribute * attr, const 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->store)
- ret = bus_attr->store(bus,buf,count,off);
+ ret = bus_attr->store(bus,buf);
return ret;
}
@@ -316,12 +312,14 @@ static int device_attach(struct device * dev)
* If bus_match() returns 0 and the @dev->driver is set, we've found
* a compatible pair, so we call devclass_add_device() to add the
* device to the class.
+ *
+ * Note that we ignore the error from bus_match(), since it's perfectly
+ * valid for a driver not to bind to any devices.
*/
static int driver_attach(struct device_driver * drv)
{
struct bus_type * bus = drv->bus;
struct list_head * entry;
- int error = 0;
if (!bus->match)
return 0;
@@ -333,7 +331,7 @@ static int driver_attach(struct device_driver * drv)
devclass_add_device(dev);
}
}
- return error;
+ return 0;
}
@@ -387,15 +385,18 @@ static void driver_detach(struct device_driver * drv)
int bus_add_device(struct device * dev)
{
struct bus_type * bus = get_bus(dev->bus);
+ int error = 0;
+
if (bus) {
down_write(&dev->bus->subsys.rwsem);
pr_debug("bus %s: add device %s\n",bus->name,dev->bus_id);
list_add_tail(&dev->bus_list,&dev->bus->devices.list);
- device_attach(dev);
+ if ((error = device_attach(dev)))
+ list_del_init(&dev->bus_list);
up_write(&dev->bus->subsys.rwsem);
sysfs_create_link(&bus->devices.kobj,&dev->kobj,dev->bus_id);
}
- return 0;
+ return error;
}
/**
@@ -429,19 +430,33 @@ void bus_remove_device(struct device * dev)
int bus_add_driver(struct device_driver * drv)
{
struct bus_type * bus = get_bus(drv->bus);
+ int error = 0;
+
if (bus) {
pr_debug("bus %s: add driver %s\n",bus->name,drv->name);
strncpy(drv->kobj.name,drv->name,KOBJ_NAME_LEN);
drv->kobj.kset = &bus->drivers;
- kobject_register(&drv->kobj);
+
+ if ((error = kobject_register(&drv->kobj))) {
+ put_bus(bus);
+ return error;
+ }
down_write(&bus->subsys.rwsem);
- devclass_add_driver(drv);
- driver_attach(drv);
+ if (!(error = devclass_add_driver(drv))) {
+ if ((error = driver_attach(drv))) {
+ devclass_remove_driver(drv);
+ }
+ }
up_write(&bus->subsys.rwsem);
+
+ if (error) {
+ kobject_unregister(&drv->kobj);
+ put_bus(bus);
+ }
}
- return 0;
+ return error;
}
diff --git a/drivers/base/class.c b/drivers/base/class.c
index 01e82cb997a5..3bf2636553c0 100644
--- a/drivers/base/class.c
+++ b/drivers/base/class.c
@@ -2,7 +2,7 @@
* class.c - basic device class management
*/
-#define DEBUG
+#undef DEBUG
#include <linux/device.h>
#include <linux/module.h>
@@ -14,28 +14,26 @@
#define to_class(obj) container_of(obj,struct device_class,subsys.kset.kobj)
static ssize_t
-devclass_attr_show(struct kobject * kobj, struct attribute * attr,
- char * buf, size_t count, loff_t off)
+devclass_attr_show(struct kobject * kobj, struct attribute * attr, char * buf)
{
struct devclass_attribute * class_attr = to_class_attr(attr);
struct device_class * dc = to_class(kobj);
ssize_t ret = 0;
if (class_attr->show)
- ret = class_attr->show(dc,buf,count,off);
+ ret = class_attr->show(dc,buf);
return ret;
}
static ssize_t
-devclass_attr_store(struct kobject * kobj, struct attribute * attr,
- const char * buf, size_t count, loff_t off)
+devclass_attr_store(struct kobject * kobj, struct attribute * attr, const char * buf)
{
struct devclass_attribute * class_attr = to_class_attr(attr);
struct device_class * dc = to_class(kobj);
ssize_t ret = 0;
if (class_attr->store)
- ret = class_attr->store(dc,buf,count,off);
+ ret = class_attr->store(dc,buf);
return ret;
}
@@ -100,15 +98,19 @@ void devclass_remove_file(struct device_class * cls, struct devclass_attribute *
int devclass_add_driver(struct device_driver * drv)
{
struct device_class * cls = get_devclass(drv->devclass);
+ int error = 0;
+
if (cls) {
down_write(&cls->subsys.rwsem);
pr_debug("device class %s: adding driver %s:%s\n",
cls->name,drv->bus->name,drv->name);
- list_add_tail(&drv->class_list,&cls->drivers.list);
- devclass_drv_link(drv);
+ error = devclass_drv_link(drv);
+
+ if (!error)
+ list_add_tail(&drv->class_list,&cls->drivers.list);
up_write(&cls->subsys.rwsem);
}
- return 0;
+ return error;
}
void devclass_remove_driver(struct device_driver * drv)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 234337528d15..8e66d54a1c27 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -35,28 +35,26 @@ DECLARE_MUTEX(device_sem);
extern struct attribute * dev_default_attrs[];
static ssize_t
-dev_attr_show(struct kobject * kobj, struct attribute * attr,
- char * buf, size_t count, loff_t off)
+dev_attr_show(struct kobject * kobj, struct attribute * attr, char * buf)
{
struct device_attribute * dev_attr = to_dev_attr(attr);
struct device * dev = to_dev(kobj);
ssize_t ret = 0;
if (dev_attr->show)
- ret = dev_attr->show(dev,buf,count,off);
+ ret = dev_attr->show(dev,buf);
return ret;
}
static ssize_t
-dev_attr_store(struct kobject * kobj, struct attribute * attr,
- const char * buf, size_t count, loff_t off)
+dev_attr_store(struct kobject * kobj, struct attribute * attr, const char * buf)
{
struct device_attribute * dev_attr = to_dev_attr(attr);
struct device * dev = to_dev(kobj);
ssize_t ret = 0;
if (dev_attr->store)
- ret = dev_attr->store(dev,buf,count,off);
+ ret = dev_attr->store(dev,buf);
return ret;
}
diff --git a/drivers/base/driver.c b/drivers/base/driver.c
index 39d9e70efc48..a7b123878ce4 100644
--- a/drivers/base/driver.c
+++ b/drivers/base/driver.c
@@ -3,7 +3,7 @@
*
*/
-#define DEBUG
+#undef DEBUG
#include <linux/device.h>
#include <linux/module.h>
diff --git a/drivers/base/interface.c b/drivers/base/interface.c
index 7d06c1e44c34..ebf359fbca74 100644
--- a/drivers/base/interface.c
+++ b/drivers/base/interface.c
@@ -10,21 +10,21 @@
#include <linux/stat.h>
#include <linux/string.h>
-static ssize_t device_read_name(struct device * dev, char * buf, size_t count, loff_t off)
+static ssize_t device_read_name(struct device * dev, char * buf)
{
- return off ? 0 : sprintf(buf,"%s\n",dev->name);
+ return sprintf(buf,"%s\n",dev->name);
}
static DEVICE_ATTR(name,S_IRUGO,device_read_name,NULL);
static ssize_t
-device_read_power(struct device * dev, char * page, size_t count, loff_t off)
+device_read_power(struct device * dev, char * page)
{
- return off ? 0 : sprintf(page,"%d\n",dev->power_state);
+ return sprintf(page,"%d\n",dev->power_state);
}
static ssize_t
-device_write_power(struct device * dev, const char * buf, size_t count, loff_t off)
+device_write_power(struct device * dev, const char * buf)
{
char str_command[20];
char str_level[20];
@@ -33,9 +33,6 @@ device_write_power(struct device * dev, const char * buf, size_t count, loff_t o
u32 int_level;
int error = 0;
- if (off)
- return 0;
-
if (!dev->driver)
goto done;
@@ -83,7 +80,7 @@ device_write_power(struct device * dev, const char * buf, size_t count, loff_t o
error = 0;
}
done:
- return error < 0 ? error : count;
+ return error < 0 ? error : strlen(buf);
}
static DEVICE_ATTR(power,S_IWUSR | S_IRUGO,
diff --git a/drivers/base/intf.c b/drivers/base/intf.c
index dbf787225621..809547aed60d 100644
--- a/drivers/base/intf.c
+++ b/drivers/base/intf.c
@@ -2,7 +2,7 @@
* intf.c - class-specific interface management
*/
-#define DEBUG
+#undef DEBUG
#include <linux/device.h>
#include <linux/module.h>
@@ -66,8 +66,7 @@ int interface_add_data(struct intf_data * data)
kobject_register(&data->kobj);
list_add_tail(&data->dev_entry,&data->dev->intf_list);
- intf_dev_link(data);
- return 0;
+ return intf_dev_link(data);
}
return -EINVAL;
}
diff --git a/drivers/block/genhd.c b/drivers/block/genhd.c
index f0eabbf2df11..8d42f19811b4 100644
--- a/drivers/block/genhd.c
+++ b/drivers/block/genhd.c
@@ -276,17 +276,17 @@ subsys_initcall(device_init);
struct disk_attribute {
struct attribute attr;
- ssize_t (*show)(struct gendisk *, char *, size_t, loff_t);
+ ssize_t (*show)(struct gendisk *, char *);
};
static ssize_t disk_attr_show(struct kobject * kobj, struct attribute * attr,
- char * page, size_t count, loff_t off)
+ char * page)
{
struct gendisk * disk = to_disk(kobj);
struct disk_attribute * disk_attr = container_of(attr,struct disk_attribute,attr);
ssize_t ret = 0;
if (disk_attr->show)
- ret = disk_attr->show(disk,page,count,off);
+ ret = disk_attr->show(disk,page);
return ret;
}
@@ -294,21 +294,18 @@ static struct sysfs_ops disk_sysfs_ops = {
.show = &disk_attr_show,
};
-static ssize_t disk_dev_read(struct gendisk * disk,
- char *page, size_t count, loff_t off)
+static ssize_t disk_dev_read(struct gendisk * disk, char *page)
{
dev_t base = MKDEV(disk->major, disk->first_minor);
- return off ? 0 : sprintf(page, "%04x\n",base);
+ return sprintf(page, "%04x\n",base);
}
-static ssize_t disk_range_read(struct gendisk * disk,
- char *page, size_t count, loff_t off)
+static ssize_t disk_range_read(struct gendisk * disk, char *page)
{
- return off ? 0 : sprintf(page, "%d\n",disk->minors);
+ return sprintf(page, "%d\n",disk->minors);
}
-static ssize_t disk_size_read(struct gendisk * disk,
- char *page, size_t count, loff_t off)
+static ssize_t disk_size_read(struct gendisk * disk, char *page)
{
- return off ? 0 : sprintf(page, "%llu\n",(unsigned long long)get_capacity(disk));
+ return sprintf(page, "%llu\n",(unsigned long long)get_capacity(disk));
}
static inline unsigned jiffies_to_msec(unsigned jif)
@@ -321,11 +318,10 @@ static inline unsigned jiffies_to_msec(unsigned jif)
return (jif / HZ) * 1000 + (jif % HZ) * 1000 / HZ;
#endif
}
-static ssize_t disk_stat_read(struct gendisk * disk,
- char *page, size_t count, loff_t off)
+static ssize_t disk_stat_read(struct gendisk * disk, char *page)
{
disk_round_stats(disk);
- return off ? 0 : sprintf(page,
+ return sprintf(page,
"%8u %8u %8llu %8u "
"%8u %8u %8llu %8u "
"%8u %8u %8u"
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 34026e3dc6be..268e5084f7a0 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -3,6 +3,7 @@
#
config PCI_LEGACY_PROC
bool "Legacy /proc/pci interface"
+ depends on PCI
---help---
This feature enables a procfs file -- /proc/pci -- that provides a
summary of PCI devices in the system.
diff --git a/drivers/pci/pool.c b/drivers/pci/pool.c
index 93cbe06c7786..f9c9d0e1a0e4 100644
--- a/drivers/pci/pool.c
+++ b/drivers/pci/pool.c
@@ -34,19 +34,16 @@ struct pci_page { /* cacheable header for 'allocation' bytes */
DECLARE_MUTEX (pools_lock);
static ssize_t
-show_pools (struct device *dev, char *buf, size_t count, loff_t off)
+show_pools (struct device *dev, char *buf)
{
struct pci_dev *pdev;
unsigned temp, size;
char *next;
struct list_head *i, *j;
- if (off != 0)
- return 0;
-
pdev = container_of (dev, struct pci_dev, dev);
next = buf;
- size = count;
+ size = PAGE_SIZE;
temp = snprintf (next, size, "poolinfo - 0.1\n");
size -= temp;
@@ -77,7 +74,7 @@ show_pools (struct device *dev, char *buf, size_t count, loff_t off)
}
up (&pools_lock);
- return count - size;
+ return PAGE_SIZE - size;
}
static DEVICE_ATTR (pools, S_IRUGO, show_pools, NULL);
diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c
index bbc437e54a0b..d20cfdc656a9 100644
--- a/drivers/pci/proc.c
+++ b/drivers/pci/proc.c
@@ -374,32 +374,25 @@ static struct seq_operations proc_bus_pci_devices_op = {
struct proc_dir_entry *proc_bus_pci_dir;
/* driverfs files */
-static ssize_t pci_show_irq(struct device * dev, char * buf, size_t count, loff_t off)
+static ssize_t pci_show_irq(struct device * dev, char * buf)
{
struct pci_dev * pci_dev = to_pci_dev(dev);
- return off ? 0 : sprintf(buf,"%u\n",pci_dev->irq);
+ return sprintf(buf,"%u\n",pci_dev->irq);
}
static DEVICE_ATTR(irq,S_IRUGO,pci_show_irq,NULL);
-static ssize_t pci_show_resources(struct device * dev, char * buf, size_t count, loff_t off)
+static ssize_t pci_show_resources(struct device * dev, char * buf)
{
struct pci_dev * pci_dev = to_pci_dev(dev);
char * str = buf;
int i;
- if (off && off < DEVICE_COUNT_RESOURCE) {
+ for (i = 0; i < DEVICE_COUNT_RESOURCE && pci_resource_start(pci_dev,i); i++) {
str += sprintf(str,LONG_FORMAT LONG_FORMAT LONG_FORMAT "\n",
- pci_resource_start(pci_dev,off),
- pci_resource_end(pci_dev,off),
- pci_resource_flags(pci_dev,off));
- } else if (!off) {
- for (i = 0; i < DEVICE_COUNT_RESOURCE && pci_resource_start(pci_dev,i); i++) {
- str += sprintf(str,LONG_FORMAT LONG_FORMAT LONG_FORMAT "\n",
- pci_resource_start(pci_dev,i),
- pci_resource_end(pci_dev,i),
- pci_resource_flags(pci_dev,i));
- }
+ pci_resource_start(pci_dev,i),
+ pci_resource_end(pci_dev,i),
+ pci_resource_flags(pci_dev,i));
}
return (str - buf);
}
diff --git a/drivers/pnp/interface.c b/drivers/pnp/interface.c
index b7ad96265091..e33e5617819c 100644
--- a/drivers/pnp/interface.c
+++ b/drivers/pnp/interface.c
@@ -215,14 +215,13 @@ static void pnp_print_resources(pnp_info_buffer_t *buffer, char *space, struct p
pnp_print_mem32(buffer, space, mem32);
}
-static ssize_t pnp_show_possible_resources(struct device *dmdev, char *buf, size_t count, loff_t off)
+static ssize_t pnp_show_possible_resources(struct device *dmdev, char *buf)
{
struct pnp_dev *dev = to_pnp_dev(dmdev);
struct pnp_resources * res = dev->res;
int dep = 0;
pnp_info_buffer_t *buffer;
- if (off)
- return 0;
+
buffer = (pnp_info_buffer_t *) pnp_alloc(sizeof(pnp_info_buffer_t));
if (!buffer)
return -ENOMEM;
@@ -242,13 +241,12 @@ static ssize_t pnp_show_possible_resources(struct device *dmdev, char *buf, size
static DEVICE_ATTR(possible,S_IRUGO,pnp_show_possible_resources,NULL);
-static ssize_t pnp_show_current_resources(struct device *dmdev, char *buf, size_t count, loff_t off)
+static ssize_t pnp_show_current_resources(struct device *dmdev, char *buf)
{
struct pnp_dev *dev = to_pnp_dev(dmdev);
char *str = buf;
int i;
- if (off)
- return 0;
+
if (!dev->active){
str += sprintf(str,"DISABLED\n");
goto done;
@@ -282,7 +280,7 @@ static ssize_t pnp_show_current_resources(struct device *dmdev, char *buf, size_
}
static ssize_t
-pnp_set_current_resources(struct device * dmdev, const char * buf, size_t count, loff_t off)
+pnp_set_current_resources(struct device * dmdev, const char * buf)
{
struct pnp_dev *dev = to_pnp_dev(dmdev);
char command[20];
@@ -290,8 +288,7 @@ pnp_set_current_resources(struct device * dmdev, const char * buf, size_t count,
int num_args;
int error = 0;
int depnum, mode = 0;
- if (off)
- return 0;
+
num_args = sscanf(buf,"%10s %i %10s",command,&depnum,type);
if (!num_args)
goto done;
@@ -328,19 +325,18 @@ pnp_set_current_resources(struct device * dmdev, const char * buf, size_t count,
goto done;
}
done:
- return error < 0 ? error : count;
+ return error < 0 ? error : strlen(buf);
}
static DEVICE_ATTR(resources,S_IRUGO | S_IWUSR,
pnp_show_current_resources,pnp_set_current_resources);
-static ssize_t pnp_show_current_ids(struct device *dmdev, char *buf, size_t count, loff_t off)
+static ssize_t pnp_show_current_ids(struct device *dmdev, char *buf)
{
char *str = buf;
struct pnp_dev *dev = to_pnp_dev(dmdev);
struct pnp_id * pos = dev->id;
- if (off)
- return 0;
+
while (pos) {
str += sprintf(str,"%s\n", pos->id);
pos = pos->next;
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 93097755fbe1..e18776421397 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -23,20 +23,16 @@
* Return:
* number of bytes written into page.
**/
-static ssize_t scsi_host_class_name_show(struct device *dev, char *page,
- size_t count, loff_t off)
+static ssize_t scsi_host_class_name_show(struct device *dev, char *page)
{
struct Scsi_Host *shost;
- if (off)
- return 0;
-
shost = to_scsi_host(dev);
if (!shost)
return 0;
- return snprintf(page, count, "scsi%d\n", shost->host_no);
+ return snprintf(page, 20, "scsi%d\n", shost->host_no);
}
DEVICE_ATTR(class_name, S_IRUGO, scsi_host_class_name_show, NULL);
@@ -138,13 +134,11 @@ void scsi_upper_driver_unregister(struct Scsi_Device_Template *sdev_tp)
*/
#define show_function(field, format_string) \
static ssize_t \
-show_##field (struct device *dev, char *buf, size_t count, loff_t off) \
+show_##field (struct device *dev, char *buf) \
{ \
struct scsi_device *sdev; \
- if (off) \
- return 0; \
sdev = to_scsi_device(dev); \
- return snprintf (buf, count, format_string, sdev->field); \
+ return snprintf (buf, 20, format_string, sdev->field); \
} \
/*
@@ -164,14 +158,12 @@ static DEVICE_ATTR(field, S_IRUGO, show_##field, NULL)
show_function(field, format_string) \
\
static ssize_t \
-store_##field (struct device *dev, const char *buf, size_t count, loff_t off)\
+store_##field (struct device *dev, const char *buf) \
{ \
struct scsi_device *sdev; \
- \
- if (off) \
- return 0; \
sdev = to_scsi_device(dev); \
- return snscanf (buf, count, format_string, &sdev->field); \
+ snscanf (buf, 20, format_string, &sdev->field); \
+ return strlen(buf); \
} \
static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, show_##field, store_##field)
@@ -183,18 +175,15 @@ static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, show_##field, store_##field)
show_function(field, "%d\n") \
\
static ssize_t \
-store_##field (struct device *dev, const char *buf, size_t count, loff_t off)\
+store_##field (struct device *dev, const char *buf) \
{ \
int ret; \
struct scsi_device *sdev; \
- \
- if (off) \
- return 0; \
ret = scsi_sdev_check_buf_bit(buf); \
if (ret >= 0) { \
sdev = to_scsi_device(dev); \
sdev->field = ret; \
- ret = count; \
+ ret = strlen(buf); \
} \
return ret; \
} \
diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
index b7efb1209e9a..63f9083f08b7 100644
--- a/drivers/scsi/sg.c
+++ b/drivers/scsi/sg.c
@@ -1350,20 +1350,18 @@ __setup("sg_def_reserved_size=", sg_def_reserved_size_setup);
/* Driverfs file support */
static ssize_t
-sg_device_kdev_read(struct device *driverfs_dev, char *page,
- size_t count, loff_t off)
+sg_device_kdev_read(struct device *driverfs_dev, char *page)
{
Sg_device *sdp = list_entry(driverfs_dev, Sg_device, sg_driverfs_dev);
- return off ? 0 : sprintf(page, "%x\n", MKDEV(sdp->disk->major,
- sdp->disk->first_minor));
+ return sprintf(page, "%x\n", MKDEV(sdp->disk->major,
+ sdp->disk->first_minor));
}
static DEVICE_ATTR(kdev,S_IRUGO,sg_device_kdev_read,NULL);
static ssize_t
-sg_device_type_read(struct device *driverfs_dev, char *page,
- size_t count, loff_t off)
+sg_device_type_read(struct device *driverfs_dev, char *page)
{
- return off ? 0 : sprintf(page, "CHR\n");
+ return sprintf(page, "CHR\n");
}
static DEVICE_ATTR(type,S_IRUGO,sg_device_type_read,NULL);
diff --git a/drivers/usb/core/driverfs.c b/drivers/usb/core/driverfs.c
index 2558ca462aa6..cd3bbc14b3e9 100644
--- a/drivers/usb/core/driverfs.c
+++ b/drivers/usb/core/driverfs.c
@@ -25,13 +25,10 @@
/* Active configuration fields */
#define usb_actconfig_attr(field, format_string) \
static ssize_t \
-show_##field (struct device *dev, char *buf, size_t count, loff_t off) \
+show_##field (struct device *dev, char *buf) \
{ \
struct usb_device *udev; \
\
- if (off) \
- return 0; \
- \
udev = to_usb_device (dev); \
return sprintf (buf, format_string, udev->actconfig->desc.field); \
} \
@@ -43,13 +40,11 @@ usb_actconfig_attr (bmAttributes, "%2x\n")
usb_actconfig_attr (bMaxPower, "%3dmA\n")
/* String fields */
-static ssize_t show_product (struct device *dev, char *buf, size_t count, loff_t off)
+static ssize_t show_product (struct device *dev, char *buf)
{
struct usb_device *udev;
int len;
- if (off)
- return 0;
udev = to_usb_device (dev);
len = usb_string(udev, udev->descriptor.iProduct, buf, PAGE_SIZE);
@@ -62,13 +57,11 @@ static ssize_t show_product (struct device *dev, char *buf, size_t count, loff_t
static DEVICE_ATTR(product,S_IRUGO,show_product,NULL);
static ssize_t
-show_manufacturer (struct device *dev, char *buf, size_t count, loff_t off)
+show_manufacturer (struct device *dev, char *buf)
{
struct usb_device *udev;
int len;
- if (off)
- return 0;
udev = to_usb_device (dev);
len = usb_string(udev, udev->descriptor.iManufacturer, buf, PAGE_SIZE);
@@ -81,13 +74,11 @@ show_manufacturer (struct device *dev, char *buf, size_t count, loff_t off)
static DEVICE_ATTR(manufacturer,S_IRUGO,show_manufacturer,NULL);
static ssize_t
-show_serial (struct device *dev, char *buf, size_t count, loff_t off)
+show_serial (struct device *dev, char *buf)
{
struct usb_device *udev;
int len;
- if (off)
- return 0;
udev = to_usb_device (dev);
len = usb_string(udev, udev->descriptor.iSerialNumber, buf, PAGE_SIZE);
@@ -100,13 +91,11 @@ show_serial (struct device *dev, char *buf, size_t count, loff_t off)
static DEVICE_ATTR(serial,S_IRUGO,show_serial,NULL);
static ssize_t
-show_speed (struct device *dev, char *buf, size_t count, loff_t off)
+show_speed (struct device *dev, char *buf)
{
struct usb_device *udev;
char *speed;
- if (off)
- return 0;
udev = to_usb_device (dev);
switch (udev->speed) {
@@ -130,13 +119,10 @@ static DEVICE_ATTR(speed, S_IRUGO, show_speed, NULL);
/* Descriptor fields */
#define usb_descriptor_attr(field, format_string) \
static ssize_t \
-show_##field (struct device *dev, char *buf, size_t count, loff_t off) \
+show_##field (struct device *dev, char *buf) \
{ \
struct usb_device *udev; \
\
- if (off) \
- return 0; \
- \
udev = to_usb_device (dev); \
return sprintf (buf, format_string, udev->descriptor.field); \
} \
@@ -185,14 +171,11 @@ void usb_create_driverfs_dev_files (struct usb_device *udev)
/* Interface fields */
#define usb_intf_attr(field, format_string) \
static ssize_t \
-show_##field (struct device *dev, char *buf, size_t count, loff_t off) \
+show_##field (struct device *dev, char *buf) \
{ \
struct usb_interface *intf; \
int alt; \
\
- if (off) \
- return 0; \
- \
intf = to_usb_interface (dev); \
alt = intf->act_altsetting; \
\
diff --git a/fs/partitions/check.c b/fs/partitions/check.c
index 2fdb53d20016..dbd42deae348 100644
--- a/fs/partitions/check.c
+++ b/fs/partitions/check.c
@@ -274,17 +274,17 @@ static void devfs_remove_partitions(struct gendisk *dev)
struct part_attribute {
struct attribute attr;
- ssize_t (*show)(struct hd_struct *,char *,size_t,loff_t);
+ ssize_t (*show)(struct hd_struct *,char *);
};
-static ssize_t part_attr_show(struct kobject * kobj, struct attribute * attr,
- char * page, size_t count, loff_t off)
+static ssize_t
+part_attr_show(struct kobject * kobj, struct attribute * attr, char * page)
{
struct hd_struct * p = container_of(kobj,struct hd_struct,kobj);
struct part_attribute * part_attr = container_of(attr,struct part_attribute,attr);
ssize_t ret = 0;
if (part_attr->show)
- ret = part_attr->show(p,page,count,off);
+ ret = part_attr->show(p,page);
return ret;
}
@@ -292,30 +292,26 @@ static struct sysfs_ops part_sysfs_ops = {
.show = part_attr_show,
};
-static ssize_t part_dev_read(struct hd_struct * p,
- char *page, size_t count, loff_t off)
+static ssize_t part_dev_read(struct hd_struct * p, char *page)
{
struct gendisk *disk = container_of(p->kobj.parent,struct gendisk,kobj);
int part = p - disk->part + 1;
dev_t base = MKDEV(disk->major, disk->first_minor);
- return off ? 0 : sprintf(page, "%04x\n",base + part);
+ return sprintf(page, "%04x\n",base + part);
}
-static ssize_t part_start_read(struct hd_struct * p,
- char *page, size_t count, loff_t off)
+static ssize_t part_start_read(struct hd_struct * p, char *page)
{
- return off ? 0 : sprintf(page, "%llu\n",(unsigned long long)p->start_sect);
+ return sprintf(page, "%llu\n",(unsigned long long)p->start_sect);
}
-static ssize_t part_size_read(struct hd_struct * p,
- char *page, size_t count, loff_t off)
+static ssize_t part_size_read(struct hd_struct * p, char *page)
{
- return off ? 0 : sprintf(page, "%llu\n",(unsigned long long)p->nr_sects);
+ return sprintf(page, "%llu\n",(unsigned long long)p->nr_sects);
}
-static ssize_t part_stat_read(struct hd_struct * p,
- char *page, size_t count, loff_t off)
+static ssize_t part_stat_read(struct hd_struct * p, char *page)
{
- return off ? 0 : sprintf(page, "%8u %8llu %8u %8llu\n",
- p->reads, (unsigned long long)p->read_sectors,
- p->writes, (unsigned long long)p->write_sectors);
+ return sprintf(page, "%8u %8llu %8u %8llu\n",
+ p->reads, (unsigned long long)p->read_sectors,
+ p->writes, (unsigned long long)p->write_sectors);
}
static struct part_attribute part_attr_dev = {
.attr = {.name = "dev", .mode = S_IRUGO },
diff --git a/fs/sysfs/inode.c b/fs/sysfs/inode.c
index 24984f4e439d..8ecf901ee6e1 100644
--- a/fs/sysfs/inode.c
+++ b/fs/sysfs/inode.c
@@ -72,6 +72,7 @@ static struct inode *sysfs_get_inode(struct super_block *sb, int mode, int dev)
init_special_inode(inode, mode, dev);
break;
case S_IFREG:
+ inode->i_size = PAGE_SIZE;
inode->i_fop = &sysfs_file_operations;
break;
case S_IFDIR:
@@ -153,27 +154,27 @@ static int sysfs_symlink(struct inode * dir, struct dentry *dentry, const char *
* These operations allow subsystems to have files that can be
* read/written.
*/
-ssize_t subsys_attr_show(struct kobject * kobj, struct attribute * attr,
- char * page, size_t count, loff_t off)
+static ssize_t
+subsys_attr_show(struct kobject * kobj, struct attribute * attr, char * page)
{
struct subsystem * s = to_subsys(kobj);
struct subsys_attribute * sattr = to_sattr(attr);
ssize_t ret = 0;
if (sattr->show)
- ret = sattr->show(s,page,count,off);
+ ret = sattr->show(s,page);
return ret;
}
-ssize_t subsys_attr_store(struct kobject * kobj, struct attribute * attr,
- const char * page, size_t count, loff_t off)
+static ssize_t
+subsys_attr_store(struct kobject * kobj, struct attribute * attr, const char * page)
{
struct subsystem * s = to_subsys(kobj);
struct subsys_attribute * sattr = to_sattr(attr);
ssize_t ret = 0;
if (sattr->store)
- ret = sattr->store(s,page,count,off);
+ ret = sattr->store(s,page);
return ret;
}
@@ -182,6 +183,73 @@ static struct sysfs_ops subsys_sysfs_ops = {
.store = subsys_attr_store,
};
+
+struct sysfs_buffer {
+ size_t count;
+ loff_t pos;
+ char * page;
+ struct sysfs_ops * ops;
+};
+
+
+/**
+ * fill_read_buffer - allocate and fill buffer from object.
+ * @file: file pointer.
+ * @buffer: data buffer for file.
+ *
+ * Allocate @buffer->page, if it hasn't been already, then call the
+ * kobject's show() method to fill the buffer with this attribute's
+ * data.
+ * This is called only once, on the file's first read.
+ */
+static int fill_read_buffer(struct file * file, struct sysfs_buffer * buffer)
+{
+ struct attribute * attr = file->f_dentry->d_fsdata;
+ struct kobject * kobj = file->f_dentry->d_parent->d_fsdata;
+ struct sysfs_ops * ops = buffer->ops;
+ int ret = 0;
+ size_t count;
+
+ if (!buffer->page)
+ buffer->page = (char *) __get_free_page(GFP_KERNEL);
+ if (!buffer->page)
+ return -ENOMEM;
+
+ count = ops->show(kobj,attr,buffer->page);
+ if (count >= 0)
+ buffer->count = count;
+ else
+ ret = count;
+ return ret;
+}
+
+
+/**
+ * flush_read_buffer - push buffer to userspace.
+ * @buffer: data buffer for file.
+ * @userbuf: user-passed buffer.
+ * @count: number of bytes requested.
+ * @ppos: file position.
+ *
+ * Copy the buffer we filled in fill_read_buffer() to userspace.
+ * This is done at the reader's leisure, copying and advancing
+ * the amount they specify each time.
+ * This may be called continuously until the buffer is empty.
+ */
+static int flush_read_buffer(struct sysfs_buffer * buffer, char * buf,
+ size_t count, loff_t * ppos)
+{
+ int error;
+
+ if (count > (buffer->count - *ppos))
+ count = buffer->count - *ppos;
+
+ error = copy_to_user(buf,buffer->page + *ppos,count);
+ if (!error)
+ *ppos += count;
+ return error ? error : count;
+}
+
/**
* sysfs_read_file - read an attribute.
* @file: file pointer.
@@ -191,55 +259,79 @@ static struct sysfs_ops subsys_sysfs_ops = {
*
* Userspace wants to read an attribute file. The attribute descriptor
* is in the file's ->d_fsdata. The target object is in the directory's
- * ->d_fsdata.
+ * ->d_fsdata.
*
- * We allocate a %PAGE_SIZE buffer, and pass it to the object's ->show()
- * method (along with the object). We loop doing this until @count is
- * satisfied, or ->show() returns %0.
+ * We call fill_read_buffer() to allocate and fill the buffer from the
+ * object's show() method exactly once (if the read is happening from
+ * the beginning of the file). That should fill the entire buffer with
+ * all the data the object has to offer for that attribute.
+ * We then call flush_read_buffer() to copy the buffer to userspace
+ * in the increments specified.
*/
static ssize_t
sysfs_read_file(struct file *file, char *buf, size_t count, loff_t *ppos)
{
- struct attribute * attr = file->f_dentry->d_fsdata;
- struct sysfs_ops * ops = file->private_data;
- struct kobject * kobj = file->f_dentry->d_parent->d_fsdata;
- unsigned char *page;
+ struct sysfs_buffer * buffer = file->private_data;
ssize_t retval = 0;
- if (count > PAGE_SIZE)
- count = PAGE_SIZE;
+ if (!*ppos) {
+ if ((retval = fill_read_buffer(file,buffer)))
+ return retval;
+ }
+ pr_debug("%s: count = %d, ppos = %lld, buf = %s\n",
+ __FUNCTION__,count,*ppos,buffer->page);
+ return flush_read_buffer(buffer,buf,count,ppos);
+}
+
+
+/**
+ * fill_write_buffer - copy buffer from userspace.
+ * @buffer: data buffer for file.
+ * @userbuf: data from user.
+ * @count: number of bytes in @userbuf.
+ *
+ * Allocate @buffer->page if it hasn't been already, then
+ * copy the user-supplied buffer into it.
+ */
+
+static int
+fill_write_buffer(struct sysfs_buffer * buffer, const char * buf, size_t count)
+{
+ int error;
- page = (unsigned char*)__get_free_page(GFP_KERNEL);
- if (!page)
+ if (!buffer->page)
+ buffer->page = (char *)__get_free_page(GFP_KERNEL);
+ if (!buffer->page)
return -ENOMEM;
- while (count > 0) {
- ssize_t len;
+ if (count >= PAGE_SIZE)
+ count = PAGE_SIZE - 1;
+ error = copy_from_user(buffer->page,buf,count);
+ return error ? error : count;
+}
- len = ops->show(kobj,attr,page,count,*ppos);
- if (len <= 0) {
- if (len < 0)
- retval = len;
- break;
- } else if (len > count)
- len = count;
+/**
+ * flush_write_buffer - push buffer to kobject.
+ * @file: file pointer.
+ * @buffer: data buffer for file.
+ *
+ * Get the correct pointers for the kobject and the attribute we're
+ * dealing with, then call the store() method for the attribute,
+ * passing the buffer that we acquired in fill_write_buffer().
+ */
- if (copy_to_user(buf,page,len)) {
- retval = -EFAULT;
- break;
- }
+static int flush_write_buffer(struct file * file, struct sysfs_buffer * buffer)
+{
+ struct attribute * attr = file->f_dentry->d_fsdata;
+ struct kobject * kobj = file->f_dentry->d_parent->d_fsdata;
+ struct sysfs_ops * ops = buffer->ops;
- *ppos += len;
- count -= len;
- buf += len;
- retval += len;
- }
- free_page((unsigned long)page);
- return retval;
+ return ops->store(kobj,attr,buffer->page);
}
+
/**
* sysfs_write_file - write an attribute.
* @file: file pointer
@@ -247,56 +339,34 @@ sysfs_read_file(struct file *file, char *buf, size_t count, loff_t *ppos)
* @count: number of bytes
* @ppos: starting offset
*
- * Identical to sysfs_read_file(), though going the opposite direction.
- * We allocate a %PAGE_SIZE buffer and copy in the userspace buffer. We
- * pass that to the object's ->store() method until we reach @count or
- * ->store() returns %0 or less.
+ * Similar to sysfs_read_file(), though working in the opposite direction.
+ * We allocate and fill the data from the user in fill_write_buffer(),
+ * then push it to the kobject in flush_write_buffer().
+ * There is no easy way for us to know if userspace is only doing a partial
+ * write, so we don't support them. We expect the entire buffer to come
+ * on the first write.
+ * Hint: if you're writing a value, first read the file, modify only the
+ * the value you're changing, then write entire buffer back.
*/
static ssize_t
sysfs_write_file(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
- struct attribute * attr = file->f_dentry->d_fsdata;
- struct sysfs_ops * ops = file->private_data;
- struct kobject * kobj = file->f_dentry->d_parent->d_fsdata;
- ssize_t retval = 0;
- char * page;
-
-
- page = (char *)__get_free_page(GFP_KERNEL);
- if (!page)
- return -ENOMEM;
-
- if (count >= PAGE_SIZE)
- count = PAGE_SIZE - 1;
- if (copy_from_user(page,buf,count))
- goto done;
- *(page + count) = '\0';
-
- while (count > 0) {
- ssize_t len;
-
- len = ops->store(kobj,attr,page + retval,count,*ppos);
-
- if (len <= 0) {
- if (len < 0)
- retval = len;
- break;
- }
- retval += len;
- count -= len;
- *ppos += len;
- buf += len;
- }
- done:
- free_page((unsigned long)page);
- return retval;
+ struct sysfs_buffer * buffer = file->private_data;
+
+ count = fill_write_buffer(buffer,buf,count);
+ if (count > 0)
+ count = flush_write_buffer(file,buffer);
+ if (count > 0)
+ *ppos += count;
+ return count;
}
static int check_perm(struct inode * inode, struct file * file)
{
struct kobject * kobj = kobject_get(file->f_dentry->d_parent->d_fsdata);
struct attribute * attr = file->f_dentry->d_fsdata;
+ struct sysfs_buffer * buffer;
struct sysfs_ops * ops = NULL;
int error = 0;
@@ -343,10 +413,16 @@ static int check_perm(struct inode * inode, struct file * file)
goto Eaccess;
}
- /* No error? Great, store the ops in file->private_data
- * for easy access in the read/write functions.
+ /* No error? Great, allocate a buffer for the file, and store it
+ * it in file->private_data for easy access.
*/
- file->private_data = ops;
+ buffer = kmalloc(sizeof(struct sysfs_buffer),GFP_KERNEL);
+ if (buffer) {
+ memset(buffer,0,sizeof(struct sysfs_buffer));
+ buffer->ops = ops;
+ file->private_data = buffer;
+ } else
+ error = -ENOMEM;
goto Done;
Einval:
@@ -358,6 +434,8 @@ static int check_perm(struct inode * inode, struct file * file)
Eperm:
error = -EPERM;
Done:
+ if (error && kobj)
+ kobject_put(kobj);
return error;
}
@@ -369,8 +447,16 @@ static int sysfs_open_file(struct inode * inode, struct file * filp)
static int sysfs_release(struct inode * inode, struct file * filp)
{
struct kobject * kobj = filp->f_dentry->d_parent->d_fsdata;
+ struct sysfs_buffer * buffer = filp->private_data;
+
if (kobj)
kobject_put(kobj);
+
+ if (buffer) {
+ if (buffer->page)
+ free_page((unsigned long)buffer->page);
+ kfree(buffer);
+ }
return 0;
}
@@ -618,7 +704,19 @@ static void hash_and_remove(struct dentry * dir, const char * name)
(victim->d_parent->d_inode == dir->d_inode)) {
simple_unlink(dir->d_inode,victim);
d_delete(victim);
+
+ pr_debug("sysfs: Removing %s (%d)\n", victim->d_name.name,
+ atomic_read(&victim->d_count));
+ /**
+ * Drop reference from initial get_dentry().
+ */
+ dput(victim);
}
+
+ /**
+ * Drop the reference acquired from get_dentry() above.
+ */
+ dput(victim);
}
up(&dir->d_inode->i_sem);
}
@@ -662,29 +760,60 @@ void sysfs_remove_link(struct kobject * kobj, char * name)
void sysfs_remove_dir(struct kobject * kobj)
{
struct list_head * node, * next;
- struct dentry * dentry = kobj->dentry;
+ struct dentry * dentry = dget(kobj->dentry);
struct dentry * parent;
if (!dentry)
return;
+ pr_debug("sysfs %s: removing dir\n",dentry->d_name.name);
parent = dget(dentry->d_parent);
down(&parent->d_inode->i_sem);
down(&dentry->d_inode->i_sem);
list_for_each_safe(node,next,&dentry->d_subdirs) {
- struct dentry * d = list_entry(node,struct dentry,d_child);
- /* make sure dentry is still there */
+ struct dentry * d = dget(list_entry(node,struct dentry,d_child));
+ /**
+ * Make sure dentry is still there
+ */
+ pr_debug(" o %s: ",d->d_name.name);
if (d->d_inode) {
+
+ pr_debug("removing");
+ /**
+ * Unlink and unhash.
+ */
simple_unlink(dentry->d_inode,d);
- d_delete(dentry);
+ d_delete(d);
+
+ /**
+ * Drop reference from initial get_dentry().
+ */
+ dput(d);
}
+ pr_debug(" done (%d)\n",atomic_read(&d->d_count));
+ /**
+ * drop reference from dget() above.
+ */
+ dput(d);
}
up(&dentry->d_inode->i_sem);
d_invalidate(dentry);
simple_rmdir(parent->d_inode,dentry);
d_delete(dentry);
+
+ pr_debug(" o %s removing done (%d)\n",dentry->d_name.name,
+ atomic_read(&dentry->d_count));
+ /**
+ * Drop reference from initial get_dentry().
+ */
+ dput(dentry);
+
+ /**
+ * Drop reference from dget() on entrance.
+ */
+ dput(dentry);
up(&parent->d_inode->i_sem);
dput(parent);
}
diff --git a/include/linux/device.h b/include/linux/device.h
index d5aa116942b8..7c4bc77b695d 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -97,8 +97,8 @@ int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
struct bus_attribute {
struct attribute attr;
- ssize_t (*show)(struct bus_type *, char * buf, size_t count, loff_t off);
- ssize_t (*store)(struct bus_type *, const char * buf, size_t count, loff_t off);
+ ssize_t (*show)(struct bus_type *, char * buf);
+ ssize_t (*store)(struct bus_type *, const char * buf);
};
#define BUS_ATTR(_name,_mode,_show,_store) \
@@ -140,8 +140,8 @@ extern void put_driver(struct device_driver * drv);
struct driver_attribute {
struct attribute attr;
- ssize_t (*show)(struct device_driver *, char * buf, size_t count, loff_t off);
- ssize_t (*store)(struct device_driver *, const char * buf, size_t count, loff_t off);
+ ssize_t (*show)(struct device_driver *, char * buf);
+ ssize_t (*store)(struct device_driver *, const char * buf);
};
#define DRIVER_ATTR(_name,_mode,_show,_store) \
@@ -181,8 +181,8 @@ extern void put_devclass(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);
+ ssize_t (*show)(struct device_class *, char * buf);
+ ssize_t (*store)(struct device_class *, const char * buf);
};
#define DEVCLASS_ATTR(_name,_str,_mode,_show,_store) \
@@ -319,8 +319,8 @@ extern void device_release_driver(struct device * dev);
struct device_attribute {
struct attribute attr;
- ssize_t (*show)(struct device * dev, char * buf, size_t count, loff_t off);
- ssize_t (*store)(struct device * dev, const char * buf, size_t count, loff_t off);
+ ssize_t (*show)(struct device * dev, char * buf);
+ ssize_t (*store)(struct device * dev, const char * buf);
};
#define DEVICE_ATTR(_name,_mode,_show,_store) \
diff --git a/include/linux/kobject.h b/include/linux/kobject.h
index ff15fe1bc8fd..0cc01658044d 100644
--- a/include/linux/kobject.h
+++ b/include/linux/kobject.h
@@ -165,8 +165,8 @@ static inline void subsys_put(struct subsystem * s)
struct subsys_attribute {
struct attribute attr;
- ssize_t (*show)(struct subsystem *, char *, size_t, loff_t);
- ssize_t (*store)(struct subsystem *, const char *, size_t, loff_t);
+ ssize_t (*show)(struct subsystem *, char *);
+ ssize_t (*store)(struct subsystem *, const char *);
};
extern int subsys_create_file(struct subsystem * , struct subsys_attribute *);
diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h
index 29c3eb8d2c9e..b3cc047255b8 100644
--- a/include/linux/sysfs.h
+++ b/include/linux/sysfs.h
@@ -17,8 +17,8 @@ struct attribute {
};
struct sysfs_ops {
- ssize_t (*show)(struct kobject *, struct attribute *,char *, size_t, loff_t);
- ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t, loff_t);
+ ssize_t (*show)(struct kobject *, struct attribute *,char *);
+ ssize_t (*store)(struct kobject *,struct attribute *,const char *);
};
extern int
diff --git a/lib/kobject.c b/lib/kobject.c
index c0857237f8e6..2c05717a26a8 100644
--- a/lib/kobject.c
+++ b/lib/kobject.c
@@ -79,6 +79,29 @@ void kobject_init(struct kobject * kobj)
kobj->kset = kset_get(kobj->kset);
}
+
+/**
+ * unlink - remove kobject from kset list.
+ * @kobj: kobject.
+ *
+ * Remove the kobject from the kset list and decrement
+ * its parent's refcount.
+ * This is separated out, so we can use it in both
+ * kobject_del() and kobject_add() on error.
+ */
+
+static void unlink(struct kobject * kobj)
+{
+ if (kobj->kset) {
+ down_write(&kobj->kset->subsys->rwsem);
+ list_del_init(&kobj->entry);
+ up_write(&kobj->kset->subsys->rwsem);
+ }
+ if (kobj->parent)
+ kobject_put(kobj->parent);
+ kobject_put(kobj);
+}
+
/**
* kobject_add - add an object to the hierarchy.
* @kobj: object.
@@ -109,8 +132,8 @@ int kobject_add(struct kobject * kobj)
up_write(&kobj->kset->subsys->rwsem);
}
error = create_dir(kobj);
- if (error && parent)
- kobject_put(parent);
+ if (error)
+ unlink(kobj);
return error;
}
@@ -140,14 +163,7 @@ int kobject_register(struct kobject * kobj)
void kobject_del(struct kobject * kobj)
{
sysfs_remove_dir(kobj);
- if (kobj->kset) {
- down_write(&kobj->kset->subsys->rwsem);
- list_del_init(&kobj->entry);
- up_write(&kobj->kset->subsys->rwsem);
- }
- if (kobj->parent)
- kobject_put(kobj->parent);
- kobject_put(kobj);
+ unlink(kobj);
}
/**
@@ -317,7 +333,7 @@ int subsystem_register(struct subsystem * s)
int error;
subsystem_init(s);
- pr_debug("subsystem %s: registering\n",s->kobj.name);
+ pr_debug("subsystem %s: registering\n",s->kset.kobj.name);
if (!(error = kset_add(&s->kset))) {
if (!s->kset.subsys)
@@ -328,7 +344,7 @@ int subsystem_register(struct subsystem * s)
void subsystem_unregister(struct subsystem * s)
{
- pr_debug("subsystem %s: unregistering\n",s->kobj.name);
+ pr_debug("subsystem %s: unregistering\n",s->kset.kobj.name);
kset_unregister(&s->kset);
}
diff --git a/net/core/dev.c b/net/core/dev.c
index 227e6d24e0ae..573121fd49c0 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2520,6 +2520,11 @@ int register_netdevice(struct net_device *dev)
if (d == dev || !strcmp(d->name, dev->name))
goto out_err;
}
+ snprintf(dev->kobj.name,KOBJ_NAME_LEN,dev->name);
+ kobj_set_kset_s(dev,net_subsys);
+ if ((ret = kobject_register(&dev->kobj)))
+ goto out_err;
+
/*
* nil rebuild_header routine,
* that should be never called and used as just bug trap.
@@ -2547,10 +2552,7 @@ int register_netdevice(struct net_device *dev)
notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);
net_run_sbin_hotplug(dev, "register");
-
- snprintf(dev->kobj.name,KOBJ_NAME_LEN,dev->name);
- kobj_set_kset_s(dev,net_subsys);
- ret = kobject_register(&dev->kobj);
+ ret = 0;
out:
return ret;
@@ -2676,8 +2678,6 @@ int unregister_netdevice(struct net_device *dev)
goto out;
}
- kobject_unregister(&dev->kobj);
-
/* Last reference is our one */
if (atomic_read(&dev->refcnt) == 1)
goto out;
@@ -2738,6 +2738,7 @@ int unregister_netdevice(struct net_device *dev)
}
}
out:
+ kobject_unregister(&dev->kobj);
dev_put(dev);
return 0;
}