mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
684 lines
18 KiB
684 lines
18 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Enclosure Services |
|
* |
|
* Copyright (C) 2008 James Bottomley <[email protected]> |
|
* |
|
**----------------------------------------------------------------------------- |
|
** |
|
** |
|
**----------------------------------------------------------------------------- |
|
*/ |
|
#include <linux/device.h> |
|
#include <linux/enclosure.h> |
|
#include <linux/err.h> |
|
#include <linux/list.h> |
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/mutex.h> |
|
#include <linux/slab.h> |
|
|
|
static LIST_HEAD(container_list); |
|
static DEFINE_MUTEX(container_list_lock); |
|
static struct class enclosure_class; |
|
|
|
/** |
|
* enclosure_find - find an enclosure given a parent device |
|
* @dev: the parent to match against |
|
* @start: Optional enclosure device to start from (NULL if none) |
|
* |
|
* Looks through the list of registered enclosures to find all those |
|
* with @dev as a parent. Returns NULL if no enclosure is |
|
* found. @start can be used as a starting point to obtain multiple |
|
* enclosures per parent (should begin with NULL and then be set to |
|
* each returned enclosure device). Obtains a reference to the |
|
* enclosure class device which must be released with device_put(). |
|
* If @start is not NULL, a reference must be taken on it which is |
|
* released before returning (this allows a loop through all |
|
* enclosures to exit with only the reference on the enclosure of |
|
* interest held). Note that the @dev may correspond to the actual |
|
* device housing the enclosure, in which case no iteration via @start |
|
* is required. |
|
*/ |
|
struct enclosure_device *enclosure_find(struct device *dev, |
|
struct enclosure_device *start) |
|
{ |
|
struct enclosure_device *edev; |
|
|
|
mutex_lock(&container_list_lock); |
|
edev = list_prepare_entry(start, &container_list, node); |
|
if (start) |
|
put_device(&start->edev); |
|
|
|
list_for_each_entry_continue(edev, &container_list, node) { |
|
struct device *parent = edev->edev.parent; |
|
/* parent might not be immediate, so iterate up to |
|
* the root of the tree if necessary */ |
|
while (parent) { |
|
if (parent == dev) { |
|
get_device(&edev->edev); |
|
mutex_unlock(&container_list_lock); |
|
return edev; |
|
} |
|
parent = parent->parent; |
|
} |
|
} |
|
mutex_unlock(&container_list_lock); |
|
|
|
return NULL; |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_find); |
|
|
|
/** |
|
* enclosure_for_each_device - calls a function for each enclosure |
|
* @fn: the function to call |
|
* @data: the data to pass to each call |
|
* |
|
* Loops over all the enclosures calling the function. |
|
* |
|
* Note, this function uses a mutex which will be held across calls to |
|
* @fn, so it must have non atomic context, and @fn may (although it |
|
* should not) sleep or otherwise cause the mutex to be held for |
|
* indefinite periods |
|
*/ |
|
int enclosure_for_each_device(int (*fn)(struct enclosure_device *, void *), |
|
void *data) |
|
{ |
|
int error = 0; |
|
struct enclosure_device *edev; |
|
|
|
mutex_lock(&container_list_lock); |
|
list_for_each_entry(edev, &container_list, node) { |
|
error = fn(edev, data); |
|
if (error) |
|
break; |
|
} |
|
mutex_unlock(&container_list_lock); |
|
|
|
return error; |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_for_each_device); |
|
|
|
/** |
|
* enclosure_register - register device as an enclosure |
|
* |
|
* @dev: device containing the enclosure |
|
* @name: chosen device name |
|
* @components: number of components in the enclosure |
|
* @cb: platform call-backs |
|
* |
|
* This sets up the device for being an enclosure. Note that @dev does |
|
* not have to be a dedicated enclosure device. It may be some other type |
|
* of device that additionally responds to enclosure services |
|
*/ |
|
struct enclosure_device * |
|
enclosure_register(struct device *dev, const char *name, int components, |
|
struct enclosure_component_callbacks *cb) |
|
{ |
|
struct enclosure_device *edev = |
|
kzalloc(struct_size(edev, component, components), GFP_KERNEL); |
|
int err, i; |
|
|
|
BUG_ON(!cb); |
|
|
|
if (!edev) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
edev->components = components; |
|
|
|
edev->edev.class = &enclosure_class; |
|
edev->edev.parent = get_device(dev); |
|
edev->cb = cb; |
|
dev_set_name(&edev->edev, "%s", name); |
|
err = device_register(&edev->edev); |
|
if (err) |
|
goto err; |
|
|
|
for (i = 0; i < components; i++) { |
|
edev->component[i].number = -1; |
|
edev->component[i].slot = -1; |
|
edev->component[i].power_status = -1; |
|
} |
|
|
|
mutex_lock(&container_list_lock); |
|
list_add_tail(&edev->node, &container_list); |
|
mutex_unlock(&container_list_lock); |
|
|
|
return edev; |
|
|
|
err: |
|
put_device(edev->edev.parent); |
|
kfree(edev); |
|
return ERR_PTR(err); |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_register); |
|
|
|
static struct enclosure_component_callbacks enclosure_null_callbacks; |
|
|
|
/** |
|
* enclosure_unregister - remove an enclosure |
|
* |
|
* @edev: the registered enclosure to remove; |
|
*/ |
|
void enclosure_unregister(struct enclosure_device *edev) |
|
{ |
|
int i; |
|
|
|
mutex_lock(&container_list_lock); |
|
list_del(&edev->node); |
|
mutex_unlock(&container_list_lock); |
|
|
|
for (i = 0; i < edev->components; i++) |
|
if (edev->component[i].number != -1) |
|
device_unregister(&edev->component[i].cdev); |
|
|
|
/* prevent any callbacks into service user */ |
|
edev->cb = &enclosure_null_callbacks; |
|
device_unregister(&edev->edev); |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_unregister); |
|
|
|
#define ENCLOSURE_NAME_SIZE 64 |
|
#define COMPONENT_NAME_SIZE 64 |
|
|
|
static void enclosure_link_name(struct enclosure_component *cdev, char *name) |
|
{ |
|
strcpy(name, "enclosure_device:"); |
|
strcat(name, dev_name(&cdev->cdev)); |
|
} |
|
|
|
static void enclosure_remove_links(struct enclosure_component *cdev) |
|
{ |
|
char name[ENCLOSURE_NAME_SIZE]; |
|
|
|
enclosure_link_name(cdev, name); |
|
|
|
/* |
|
* In odd circumstances, like multipath devices, something else may |
|
* already have removed the links, so check for this condition first. |
|
*/ |
|
if (cdev->dev->kobj.sd) |
|
sysfs_remove_link(&cdev->dev->kobj, name); |
|
|
|
if (cdev->cdev.kobj.sd) |
|
sysfs_remove_link(&cdev->cdev.kobj, "device"); |
|
} |
|
|
|
static int enclosure_add_links(struct enclosure_component *cdev) |
|
{ |
|
int error; |
|
char name[ENCLOSURE_NAME_SIZE]; |
|
|
|
error = sysfs_create_link(&cdev->cdev.kobj, &cdev->dev->kobj, "device"); |
|
if (error) |
|
return error; |
|
|
|
enclosure_link_name(cdev, name); |
|
error = sysfs_create_link(&cdev->dev->kobj, &cdev->cdev.kobj, name); |
|
if (error) |
|
sysfs_remove_link(&cdev->cdev.kobj, "device"); |
|
|
|
return error; |
|
} |
|
|
|
static void enclosure_release(struct device *cdev) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev); |
|
|
|
put_device(cdev->parent); |
|
kfree(edev); |
|
} |
|
|
|
static void enclosure_component_release(struct device *dev) |
|
{ |
|
struct enclosure_component *cdev = to_enclosure_component(dev); |
|
|
|
if (cdev->dev) { |
|
enclosure_remove_links(cdev); |
|
put_device(cdev->dev); |
|
} |
|
put_device(dev->parent); |
|
} |
|
|
|
static struct enclosure_component * |
|
enclosure_component_find_by_name(struct enclosure_device *edev, |
|
const char *name) |
|
{ |
|
int i; |
|
const char *cname; |
|
struct enclosure_component *ecomp; |
|
|
|
if (!edev || !name || !name[0]) |
|
return NULL; |
|
|
|
for (i = 0; i < edev->components; i++) { |
|
ecomp = &edev->component[i]; |
|
cname = dev_name(&ecomp->cdev); |
|
if (ecomp->number != -1 && |
|
cname && cname[0] && |
|
!strcmp(cname, name)) |
|
return ecomp; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static const struct attribute_group *enclosure_component_groups[]; |
|
|
|
/** |
|
* enclosure_component_alloc - prepare a new enclosure component |
|
* @edev: the enclosure to add the component |
|
* @number: the device number |
|
* @type: the type of component being added |
|
* @name: an optional name to appear in sysfs (leave NULL if none) |
|
* |
|
* The name is optional for enclosures that give their components a unique |
|
* name. If not, leave the field NULL and a name will be assigned. |
|
* |
|
* Returns a pointer to the enclosure component or an error. |
|
*/ |
|
struct enclosure_component * |
|
enclosure_component_alloc(struct enclosure_device *edev, |
|
unsigned int number, |
|
enum enclosure_component_type type, |
|
const char *name) |
|
{ |
|
struct enclosure_component *ecomp; |
|
struct device *cdev; |
|
int i; |
|
char newname[COMPONENT_NAME_SIZE]; |
|
|
|
if (number >= edev->components) |
|
return ERR_PTR(-EINVAL); |
|
|
|
ecomp = &edev->component[number]; |
|
|
|
if (ecomp->number != -1) |
|
return ERR_PTR(-EINVAL); |
|
|
|
ecomp->type = type; |
|
ecomp->number = number; |
|
cdev = &ecomp->cdev; |
|
cdev->parent = get_device(&edev->edev); |
|
|
|
if (name && name[0]) { |
|
/* Some hardware (e.g. enclosure in RX300 S6) has components |
|
* with non unique names. Registering duplicates in sysfs |
|
* will lead to warnings during bootup. So make the names |
|
* unique by appending consecutive numbers -1, -2, ... */ |
|
i = 1; |
|
snprintf(newname, COMPONENT_NAME_SIZE, |
|
"%s", name); |
|
while (enclosure_component_find_by_name(edev, newname)) |
|
snprintf(newname, COMPONENT_NAME_SIZE, |
|
"%s-%i", name, i++); |
|
dev_set_name(cdev, "%s", newname); |
|
} else |
|
dev_set_name(cdev, "%u", number); |
|
|
|
cdev->release = enclosure_component_release; |
|
cdev->groups = enclosure_component_groups; |
|
|
|
return ecomp; |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_component_alloc); |
|
|
|
/** |
|
* enclosure_component_register - publishes an initialized enclosure component |
|
* @ecomp: component to add |
|
* |
|
* Returns 0 on successful registration, releases the component otherwise |
|
*/ |
|
int enclosure_component_register(struct enclosure_component *ecomp) |
|
{ |
|
struct device *cdev; |
|
int err; |
|
|
|
cdev = &ecomp->cdev; |
|
err = device_register(cdev); |
|
if (err) { |
|
ecomp->number = -1; |
|
put_device(cdev); |
|
return err; |
|
} |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_component_register); |
|
|
|
/** |
|
* enclosure_add_device - add a device as being part of an enclosure |
|
* @edev: the enclosure device being added to. |
|
* @component: the number of the component |
|
* @dev: the device being added |
|
* |
|
* Declares a real device to reside in slot (or identifier) @num of an |
|
* enclosure. This will cause the relevant sysfs links to appear. |
|
* This function may also be used to change a device associated with |
|
* an enclosure without having to call enclosure_remove_device() in |
|
* between. |
|
* |
|
* Returns zero on success or an error. |
|
*/ |
|
int enclosure_add_device(struct enclosure_device *edev, int component, |
|
struct device *dev) |
|
{ |
|
struct enclosure_component *cdev; |
|
int err; |
|
|
|
if (!edev || component >= edev->components) |
|
return -EINVAL; |
|
|
|
cdev = &edev->component[component]; |
|
|
|
if (cdev->dev == dev) |
|
return -EEXIST; |
|
|
|
if (cdev->dev) { |
|
enclosure_remove_links(cdev); |
|
put_device(cdev->dev); |
|
} |
|
cdev->dev = get_device(dev); |
|
err = enclosure_add_links(cdev); |
|
if (err) { |
|
put_device(cdev->dev); |
|
cdev->dev = NULL; |
|
} |
|
return err; |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_add_device); |
|
|
|
/** |
|
* enclosure_remove_device - remove a device from an enclosure |
|
* @edev: the enclosure device |
|
* @dev: device to remove/put |
|
* |
|
* Returns zero on success or an error. |
|
* |
|
*/ |
|
int enclosure_remove_device(struct enclosure_device *edev, struct device *dev) |
|
{ |
|
struct enclosure_component *cdev; |
|
int i; |
|
|
|
if (!edev || !dev) |
|
return -EINVAL; |
|
|
|
for (i = 0; i < edev->components; i++) { |
|
cdev = &edev->component[i]; |
|
if (cdev->dev == dev) { |
|
enclosure_remove_links(cdev); |
|
put_device(dev); |
|
cdev->dev = NULL; |
|
return 0; |
|
} |
|
} |
|
return -ENODEV; |
|
} |
|
EXPORT_SYMBOL_GPL(enclosure_remove_device); |
|
|
|
/* |
|
* sysfs pieces below |
|
*/ |
|
|
|
static ssize_t components_show(struct device *cdev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev); |
|
|
|
return snprintf(buf, 40, "%d\n", edev->components); |
|
} |
|
static DEVICE_ATTR_RO(components); |
|
|
|
static ssize_t id_show(struct device *cdev, |
|
struct device_attribute *attr, |
|
char *buf) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev); |
|
|
|
if (edev->cb->show_id) |
|
return edev->cb->show_id(edev, buf); |
|
return -EINVAL; |
|
} |
|
static DEVICE_ATTR_RO(id); |
|
|
|
static struct attribute *enclosure_class_attrs[] = { |
|
&dev_attr_components.attr, |
|
&dev_attr_id.attr, |
|
NULL, |
|
}; |
|
ATTRIBUTE_GROUPS(enclosure_class); |
|
|
|
static struct class enclosure_class = { |
|
.name = "enclosure", |
|
.owner = THIS_MODULE, |
|
.dev_release = enclosure_release, |
|
.dev_groups = enclosure_class_groups, |
|
}; |
|
|
|
static const char *const enclosure_status[] = { |
|
[ENCLOSURE_STATUS_UNSUPPORTED] = "unsupported", |
|
[ENCLOSURE_STATUS_OK] = "OK", |
|
[ENCLOSURE_STATUS_CRITICAL] = "critical", |
|
[ENCLOSURE_STATUS_NON_CRITICAL] = "non-critical", |
|
[ENCLOSURE_STATUS_UNRECOVERABLE] = "unrecoverable", |
|
[ENCLOSURE_STATUS_NOT_INSTALLED] = "not installed", |
|
[ENCLOSURE_STATUS_UNKNOWN] = "unknown", |
|
[ENCLOSURE_STATUS_UNAVAILABLE] = "unavailable", |
|
[ENCLOSURE_STATUS_MAX] = NULL, |
|
}; |
|
|
|
static const char *const enclosure_type[] = { |
|
[ENCLOSURE_COMPONENT_DEVICE] = "device", |
|
[ENCLOSURE_COMPONENT_ARRAY_DEVICE] = "array device", |
|
}; |
|
|
|
static ssize_t get_component_fault(struct device *cdev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
|
|
if (edev->cb->get_fault) |
|
edev->cb->get_fault(edev, ecomp); |
|
return snprintf(buf, 40, "%d\n", ecomp->fault); |
|
} |
|
|
|
static ssize_t set_component_fault(struct device *cdev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
int val = simple_strtoul(buf, NULL, 0); |
|
|
|
if (edev->cb->set_fault) |
|
edev->cb->set_fault(edev, ecomp, val); |
|
return count; |
|
} |
|
|
|
static ssize_t get_component_status(struct device *cdev, |
|
struct device_attribute *attr,char *buf) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
|
|
if (edev->cb->get_status) |
|
edev->cb->get_status(edev, ecomp); |
|
return snprintf(buf, 40, "%s\n", enclosure_status[ecomp->status]); |
|
} |
|
|
|
static ssize_t set_component_status(struct device *cdev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
int i; |
|
|
|
for (i = 0; enclosure_status[i]; i++) { |
|
if (strncmp(buf, enclosure_status[i], |
|
strlen(enclosure_status[i])) == 0 && |
|
(buf[strlen(enclosure_status[i])] == '\n' || |
|
buf[strlen(enclosure_status[i])] == '\0')) |
|
break; |
|
} |
|
|
|
if (enclosure_status[i] && edev->cb->set_status) { |
|
edev->cb->set_status(edev, ecomp, i); |
|
return count; |
|
} else |
|
return -EINVAL; |
|
} |
|
|
|
static ssize_t get_component_active(struct device *cdev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
|
|
if (edev->cb->get_active) |
|
edev->cb->get_active(edev, ecomp); |
|
return snprintf(buf, 40, "%d\n", ecomp->active); |
|
} |
|
|
|
static ssize_t set_component_active(struct device *cdev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
int val = simple_strtoul(buf, NULL, 0); |
|
|
|
if (edev->cb->set_active) |
|
edev->cb->set_active(edev, ecomp, val); |
|
return count; |
|
} |
|
|
|
static ssize_t get_component_locate(struct device *cdev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
|
|
if (edev->cb->get_locate) |
|
edev->cb->get_locate(edev, ecomp); |
|
return snprintf(buf, 40, "%d\n", ecomp->locate); |
|
} |
|
|
|
static ssize_t set_component_locate(struct device *cdev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
int val = simple_strtoul(buf, NULL, 0); |
|
|
|
if (edev->cb->set_locate) |
|
edev->cb->set_locate(edev, ecomp, val); |
|
return count; |
|
} |
|
|
|
static ssize_t get_component_power_status(struct device *cdev, |
|
struct device_attribute *attr, |
|
char *buf) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
|
|
if (edev->cb->get_power_status) |
|
edev->cb->get_power_status(edev, ecomp); |
|
|
|
/* If still uninitialized, the callback failed or does not exist. */ |
|
if (ecomp->power_status == -1) |
|
return (edev->cb->get_power_status) ? -EIO : -ENOTTY; |
|
|
|
return snprintf(buf, 40, "%s\n", ecomp->power_status ? "on" : "off"); |
|
} |
|
|
|
static ssize_t set_component_power_status(struct device *cdev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct enclosure_device *edev = to_enclosure_device(cdev->parent); |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
int val; |
|
|
|
if (strncmp(buf, "on", 2) == 0 && |
|
(buf[2] == '\n' || buf[2] == '\0')) |
|
val = 1; |
|
else if (strncmp(buf, "off", 3) == 0 && |
|
(buf[3] == '\n' || buf[3] == '\0')) |
|
val = 0; |
|
else |
|
return -EINVAL; |
|
|
|
if (edev->cb->set_power_status) |
|
edev->cb->set_power_status(edev, ecomp, val); |
|
return count; |
|
} |
|
|
|
static ssize_t get_component_type(struct device *cdev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
|
|
return snprintf(buf, 40, "%s\n", enclosure_type[ecomp->type]); |
|
} |
|
|
|
static ssize_t get_component_slot(struct device *cdev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct enclosure_component *ecomp = to_enclosure_component(cdev); |
|
int slot; |
|
|
|
/* if the enclosure does not override then use 'number' as a stand-in */ |
|
if (ecomp->slot >= 0) |
|
slot = ecomp->slot; |
|
else |
|
slot = ecomp->number; |
|
|
|
return snprintf(buf, 40, "%d\n", slot); |
|
} |
|
|
|
static DEVICE_ATTR(fault, S_IRUGO | S_IWUSR, get_component_fault, |
|
set_component_fault); |
|
static DEVICE_ATTR(status, S_IRUGO | S_IWUSR, get_component_status, |
|
set_component_status); |
|
static DEVICE_ATTR(active, S_IRUGO | S_IWUSR, get_component_active, |
|
set_component_active); |
|
static DEVICE_ATTR(locate, S_IRUGO | S_IWUSR, get_component_locate, |
|
set_component_locate); |
|
static DEVICE_ATTR(power_status, S_IRUGO | S_IWUSR, get_component_power_status, |
|
set_component_power_status); |
|
static DEVICE_ATTR(type, S_IRUGO, get_component_type, NULL); |
|
static DEVICE_ATTR(slot, S_IRUGO, get_component_slot, NULL); |
|
|
|
static struct attribute *enclosure_component_attrs[] = { |
|
&dev_attr_fault.attr, |
|
&dev_attr_status.attr, |
|
&dev_attr_active.attr, |
|
&dev_attr_locate.attr, |
|
&dev_attr_power_status.attr, |
|
&dev_attr_type.attr, |
|
&dev_attr_slot.attr, |
|
NULL |
|
}; |
|
ATTRIBUTE_GROUPS(enclosure_component); |
|
|
|
static int __init enclosure_init(void) |
|
{ |
|
return class_register(&enclosure_class); |
|
} |
|
|
|
static void __exit enclosure_exit(void) |
|
{ |
|
class_unregister(&enclosure_class); |
|
} |
|
|
|
module_init(enclosure_init); |
|
module_exit(enclosure_exit); |
|
|
|
MODULE_AUTHOR("James Bottomley"); |
|
MODULE_DESCRIPTION("Enclosure Services"); |
|
MODULE_LICENSE("GPL v2");
|
|
|