|  | /* | 
|  | *	Configuration OSM | 
|  | * | 
|  | *	Copyright (C) 2005	Markus Lidel <Markus.Lidel@shadowconnect.com> | 
|  | * | 
|  | *	This program is free software; you can redistribute it and/or modify it | 
|  | *	under the terms of the GNU General Public License as published by the | 
|  | *	Free Software Foundation; either version 2 of the License, or (at your | 
|  | *	option) any later version. | 
|  | * | 
|  | *	Fixes/additions: | 
|  | *		Markus Lidel <Markus.Lidel@shadowconnect.com> | 
|  | *			initial version. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/i2o.h> | 
|  | #include <linux/dcache.h> | 
|  | #include <linux/namei.h> | 
|  | #include <linux/fs.h> | 
|  |  | 
|  | #include <asm/uaccess.h> | 
|  |  | 
|  | #define OSM_NAME	"config-osm" | 
|  | #define OSM_VERSION	"1.248" | 
|  | #define OSM_DESCRIPTION	"I2O Configuration OSM" | 
|  |  | 
|  | /* access mode user rw */ | 
|  | #define S_IWRSR (S_IRUSR | S_IWUSR) | 
|  |  | 
|  | static struct i2o_driver i2o_config_driver; | 
|  |  | 
|  | /* Special file operations for sysfs */ | 
|  | struct fops_attribute { | 
|  | struct bin_attribute bin; | 
|  | struct file_operations fops; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	sysfs_read_dummy | 
|  | */ | 
|  | static ssize_t sysfs_read_dummy(struct kobject *kobj, char *buf, loff_t offset, | 
|  | size_t count) | 
|  | { | 
|  | return 0; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	sysfs_write_dummy | 
|  | */ | 
|  | static ssize_t sysfs_write_dummy(struct kobject *kobj, char *buf, loff_t offset, | 
|  | size_t count) | 
|  | { | 
|  | return 0; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	sysfs_create_fops_file - Creates attribute with special file operations | 
|  | *	@kobj: kobject which should contains the attribute | 
|  | *	@attr: attributes which should be used to create file | 
|  | * | 
|  | *	First creates attribute @attr in kobject @kobj. If it is the first time | 
|  | *	this function is called, merge old fops from sysfs with new one and | 
|  | *	write it back. Afterwords the new fops will be set for the created | 
|  | *	attribute. | 
|  | * | 
|  | *	Returns 0 on success or negative error code on failure. | 
|  | */ | 
|  | static int sysfs_create_fops_file(struct kobject *kobj, | 
|  | struct fops_attribute *attr) | 
|  | { | 
|  | struct file_operations tmp, *fops; | 
|  | struct dentry *d; | 
|  | struct qstr qstr; | 
|  | int rc; | 
|  |  | 
|  | fops = &attr->fops; | 
|  |  | 
|  | if (fops->read) | 
|  | attr->bin.read = sysfs_read_dummy; | 
|  |  | 
|  | if (fops->write) | 
|  | attr->bin.write = sysfs_write_dummy; | 
|  |  | 
|  | if ((rc = sysfs_create_bin_file(kobj, &attr->bin))) | 
|  | return rc; | 
|  |  | 
|  | qstr.name = attr->bin.attr.name; | 
|  | qstr.len = strlen(qstr.name); | 
|  | qstr.hash = full_name_hash(qstr.name, qstr.len); | 
|  |  | 
|  | if ((d = lookup_hash(&qstr, kobj->dentry))) { | 
|  | if (!fops->owner) { | 
|  | memcpy(&tmp, d->d_inode->i_fop, sizeof(tmp)); | 
|  | if (fops->read) | 
|  | tmp.read = fops->read; | 
|  | if (fops->write) | 
|  | tmp.write = fops->write; | 
|  | memcpy(fops, &tmp, sizeof(tmp)); | 
|  | } | 
|  |  | 
|  | d->d_inode->i_fop = fops; | 
|  | } else | 
|  | sysfs_remove_bin_file(kobj, &attr->bin); | 
|  |  | 
|  | return -ENOENT; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	sysfs_remove_fops_file - Remove attribute with special file operations | 
|  | *	@kobj: kobject which contains the attribute | 
|  | *	@attr: attributes which are used to create file | 
|  | * | 
|  | *	Only wrapper arround sysfs_remove_bin_file() | 
|  | * | 
|  | *	Returns 0 on success or negative error code on failure. | 
|  | */ | 
|  | static inline int sysfs_remove_fops_file(struct kobject *kobj, | 
|  | struct fops_attribute *attr) | 
|  | { | 
|  | return sysfs_remove_bin_file(kobj, &attr->bin); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	i2o_config_read_hrt - Returns the HRT of the controller | 
|  | *	@kob: kernel object handle | 
|  | *	@buf: buffer into which the HRT should be copied | 
|  | *	@off: file offset | 
|  | *	@count: number of bytes to read | 
|  | * | 
|  | *	Put @count bytes starting at @off into @buf from the HRT of the I2O | 
|  | *	controller corresponding to @kobj. | 
|  | * | 
|  | *	Returns number of bytes copied into buffer. | 
|  | */ | 
|  | static ssize_t i2o_config_read_hrt(struct kobject *kobj, char *buf, | 
|  | loff_t offset, size_t count) | 
|  | { | 
|  | struct i2o_controller *c = kobj_to_i2o_device(kobj)->iop; | 
|  | i2o_hrt *hrt = c->hrt.virt; | 
|  |  | 
|  | u32 size = (hrt->num_entries * hrt->entry_len + 2) * 4; | 
|  |  | 
|  | if (offset > size) | 
|  | return 0; | 
|  |  | 
|  | if (offset + count > size) | 
|  | count = size - offset; | 
|  |  | 
|  | memcpy(buf, (u8 *) hrt + offset, count); | 
|  |  | 
|  | return count; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	i2o_config_read_lct - Returns the LCT of the controller | 
|  | *	@kob: kernel object handle | 
|  | *	@buf: buffer into which the LCT should be copied | 
|  | *	@off: file offset | 
|  | *	@count: number of bytes to read | 
|  | * | 
|  | *	Put @count bytes starting at @off into @buf from the LCT of the I2O | 
|  | *	controller corresponding to @kobj. | 
|  | * | 
|  | *	Returns number of bytes copied into buffer. | 
|  | */ | 
|  | static ssize_t i2o_config_read_lct(struct kobject *kobj, char *buf, | 
|  | loff_t offset, size_t count) | 
|  | { | 
|  | struct i2o_controller *c = kobj_to_i2o_device(kobj)->iop; | 
|  | u32 size = c->lct->table_size * 4; | 
|  |  | 
|  | if (offset > size) | 
|  | return 0; | 
|  |  | 
|  | if (offset + count > size) | 
|  | count = size - offset; | 
|  |  | 
|  | memcpy(buf, (u8 *) c->lct + offset, count); | 
|  |  | 
|  | return count; | 
|  | }; | 
|  |  | 
|  | #define I2O_CONFIG_SW_ATTR(_name,_mode,_type,_swid) \ | 
|  | static ssize_t i2o_config_##_name##_read(struct file *file, char __user *buf, size_t count, loff_t * offset) { \ | 
|  | return i2o_config_sw_read(file, buf, count, offset, _type, _swid); \ | 
|  | };\ | 
|  | \ | 
|  | static ssize_t i2o_config_##_name##_write(struct file *file, const char __user *buf, size_t count, loff_t * offset) { \ | 
|  | return i2o_config_sw_write(file, buf, count, offset, _type, _swid); \ | 
|  | }; \ | 
|  | \ | 
|  | static struct fops_attribute i2o_config_attr_##_name = { \ | 
|  | .bin = { .attr = { .name = __stringify(_name), .mode = _mode, \ | 
|  | .owner = THIS_MODULE }, \ | 
|  | .size = 0, }, \ | 
|  | .fops = { .write = i2o_config_##_name##_write, \ | 
|  | .read = i2o_config_##_name##_read} \ | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  |  | 
|  | /** | 
|  | *	i2o_config_dpt_reagion - Converts type and id to flash region | 
|  | *	@swtype: type of software module reading | 
|  | *	@swid: id of software which should be read | 
|  | * | 
|  | *	Converts type and id from I2O spec to the matching region for DPT / | 
|  | *	Adaptec controllers. | 
|  | * | 
|  | *	Returns region which match type and id or -1 on error. | 
|  | */ | 
|  | static u32 i2o_config_dpt_region(u8 swtype, u8 swid) | 
|  | { | 
|  | switch (swtype) { | 
|  | case I2O_SOFTWARE_MODULE_IRTOS: | 
|  | /* | 
|  | * content: operation firmware | 
|  | * region size: | 
|  | *      0xbc000 for 2554, 3754, 2564, 3757 | 
|  | *      0x170000 for 2865 | 
|  | *      0x17c000 for 3966 | 
|  | */ | 
|  | if (!swid) | 
|  | return 0; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case I2O_SOFTWARE_MODULE_IOP_PRIVATE: | 
|  | /* | 
|  | * content: BIOS and SMOR | 
|  | * BIOS size: first 0x8000 bytes | 
|  | * region size: | 
|  | *      0x40000 for 2554, 3754, 2564, 3757 | 
|  | *      0x80000 for 2865, 3966 | 
|  | */ | 
|  | if (!swid) | 
|  | return 1; | 
|  |  | 
|  | break; | 
|  |  | 
|  | case I2O_SOFTWARE_MODULE_IOP_CONFIG: | 
|  | switch (swid) { | 
|  | case 0: | 
|  | /* | 
|  | * content: NVRAM defaults | 
|  | * region size: 0x2000 bytes | 
|  | */ | 
|  | return 2; | 
|  | case 1: | 
|  | /* | 
|  | * content: serial number | 
|  | * region size: 0x2000 bytes | 
|  | */ | 
|  | return 3; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | return -1; | 
|  | }; | 
|  |  | 
|  | #endif | 
|  |  | 
|  | /** | 
|  | *	i2o_config_sw_read - Read a software module from controller | 
|  | *	@file: file pointer | 
|  | *	@buf: buffer into which the data should be copied | 
|  | *	@count: number of bytes to read | 
|  | *	@off: file offset | 
|  | *	@swtype: type of software module reading | 
|  | *	@swid: id of software which should be read | 
|  | * | 
|  | *	Transfers @count bytes at offset @offset from IOP into buffer using | 
|  | *	type @swtype and id @swid as described in I2O spec. | 
|  | * | 
|  | *	Returns number of bytes copied into buffer or error code on failure. | 
|  | */ | 
|  | static ssize_t i2o_config_sw_read(struct file *file, char __user * buf, | 
|  | size_t count, loff_t * offset, u8 swtype, | 
|  | u32 swid) | 
|  | { | 
|  | struct sysfs_dirent *sd = file->f_dentry->d_parent->d_fsdata; | 
|  | struct kobject *kobj = sd->s_element; | 
|  | struct i2o_controller *c = kobj_to_i2o_device(kobj)->iop; | 
|  | u32 m, function = I2O_CMD_SW_UPLOAD; | 
|  | struct i2o_dma buffer; | 
|  | struct i2o_message __iomem *msg; | 
|  | u32 __iomem *mptr; | 
|  | int rc, status; | 
|  |  | 
|  | m = i2o_msg_get_wait(c, &msg, I2O_TIMEOUT_MESSAGE_GET); | 
|  | if (m == I2O_QUEUE_EMPTY) | 
|  | return -EBUSY; | 
|  |  | 
|  | mptr = &msg->body[3]; | 
|  |  | 
|  | if ((rc = i2o_dma_alloc(&c->pdev->dev, &buffer, count, GFP_KERNEL))) { | 
|  | i2o_msg_nop(c, m); | 
|  | return rc; | 
|  | } | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  | if (c->adaptec) { | 
|  | mptr = &msg->body[4]; | 
|  | function = I2O_CMD_PRIVATE; | 
|  |  | 
|  | writel(TEN_WORD_MSG_SIZE | SGL_OFFSET_8, &msg->u.head[0]); | 
|  |  | 
|  | writel(I2O_VENDOR_DPT << 16 | I2O_DPT_FLASH_READ, | 
|  | &msg->body[0]); | 
|  | writel(i2o_config_dpt_region(swtype, swid), &msg->body[1]); | 
|  | writel(*offset, &msg->body[2]); | 
|  | writel(count, &msg->body[3]); | 
|  | } else | 
|  | #endif | 
|  | writel(NINE_WORD_MSG_SIZE | SGL_OFFSET_7, &msg->u.head[0]); | 
|  |  | 
|  | writel(0xD0000000 | count, mptr++); | 
|  | writel(buffer.phys, mptr); | 
|  |  | 
|  | writel(function << 24 | HOST_TID << 12 | ADAPTER_TID, &msg->u.head[1]); | 
|  | writel(i2o_config_driver.context, &msg->u.head[2]); | 
|  | writel(0, &msg->u.head[3]); | 
|  |  | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  | if (!c->adaptec) | 
|  | #endif | 
|  | { | 
|  | writel((u32) swtype << 16 | (u32) 1 << 8, &msg->body[0]); | 
|  | writel(0, &msg->body[1]); | 
|  | writel(swid, &msg->body[2]); | 
|  | } | 
|  |  | 
|  | status = i2o_msg_post_wait_mem(c, m, 60, &buffer); | 
|  |  | 
|  | if (status == I2O_POST_WAIT_OK) { | 
|  | if (!(rc = copy_to_user(buf, buffer.virt, count))) { | 
|  | rc = count; | 
|  | *offset += count; | 
|  | } | 
|  | } else | 
|  | rc = -EIO; | 
|  |  | 
|  | if (status != -ETIMEDOUT) | 
|  | i2o_dma_free(&c->pdev->dev, &buffer); | 
|  |  | 
|  | return rc; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	i2o_config_sw_write - Write a software module to controller | 
|  | *	@file: file pointer | 
|  | *	@buf: buffer into which the data should be copied | 
|  | *	@count: number of bytes to read | 
|  | *	@off: file offset | 
|  | *	@swtype: type of software module writing | 
|  | *	@swid: id of software which should be written | 
|  | * | 
|  | *	Transfers @count bytes at offset @offset from buffer to IOP using | 
|  | *	type @swtype and id @swid as described in I2O spec. | 
|  | * | 
|  | *	Returns number of bytes copied from buffer or error code on failure. | 
|  | */ | 
|  | static ssize_t i2o_config_sw_write(struct file *file, const char __user * buf, | 
|  | size_t count, loff_t * offset, u8 swtype, | 
|  | u32 swid) | 
|  | { | 
|  | struct sysfs_dirent *sd = file->f_dentry->d_parent->d_fsdata; | 
|  | struct kobject *kobj = sd->s_element; | 
|  | struct i2o_controller *c = kobj_to_i2o_device(kobj)->iop; | 
|  | u32 m, function = I2O_CMD_SW_DOWNLOAD; | 
|  | struct i2o_dma buffer; | 
|  | struct i2o_message __iomem *msg; | 
|  | u32 __iomem *mptr; | 
|  | int rc, status; | 
|  |  | 
|  | m = i2o_msg_get_wait(c, &msg, I2O_TIMEOUT_MESSAGE_GET); | 
|  | if (m == I2O_QUEUE_EMPTY) | 
|  | return -EBUSY; | 
|  |  | 
|  | mptr = &msg->body[3]; | 
|  |  | 
|  | if ((rc = i2o_dma_alloc(&c->pdev->dev, &buffer, count, GFP_KERNEL))) | 
|  | goto nop_msg; | 
|  |  | 
|  | if ((rc = copy_from_user(buffer.virt, buf, count))) | 
|  | goto free_buffer; | 
|  |  | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  | if (c->adaptec) { | 
|  | mptr = &msg->body[4]; | 
|  | function = I2O_CMD_PRIVATE; | 
|  |  | 
|  | writel(TEN_WORD_MSG_SIZE | SGL_OFFSET_8, &msg->u.head[0]); | 
|  |  | 
|  | writel(I2O_VENDOR_DPT << 16 | I2O_DPT_FLASH_WRITE, | 
|  | &msg->body[0]); | 
|  | writel(i2o_config_dpt_region(swtype, swid), &msg->body[1]); | 
|  | writel(*offset, &msg->body[2]); | 
|  | writel(count, &msg->body[3]); | 
|  | } else | 
|  | #endif | 
|  | writel(NINE_WORD_MSG_SIZE | SGL_OFFSET_7, &msg->u.head[0]); | 
|  |  | 
|  | writel(0xD4000000 | count, mptr++); | 
|  | writel(buffer.phys, mptr); | 
|  |  | 
|  | writel(function << 24 | HOST_TID << 12 | ADAPTER_TID, &msg->u.head[1]); | 
|  | writel(i2o_config_driver.context, &msg->u.head[2]); | 
|  | writel(0, &msg->u.head[3]); | 
|  |  | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  | if (!c->adaptec) | 
|  | #endif | 
|  | { | 
|  | writel((u32) swtype << 16 | (u32) 1 << 8, &msg->body[0]); | 
|  | writel(0, &msg->body[1]); | 
|  | writel(swid, &msg->body[2]); | 
|  | } | 
|  |  | 
|  | status = i2o_msg_post_wait_mem(c, m, 60, &buffer); | 
|  |  | 
|  | if (status != -ETIMEDOUT) | 
|  | i2o_dma_free(&c->pdev->dev, &buffer); | 
|  |  | 
|  | if (status != I2O_POST_WAIT_OK) | 
|  | return -EIO; | 
|  |  | 
|  | *offset += count; | 
|  |  | 
|  | return count; | 
|  |  | 
|  | free_buffer: | 
|  | i2o_dma_free(&c->pdev->dev, &buffer); | 
|  |  | 
|  | nop_msg: | 
|  | i2o_msg_nop(c, m); | 
|  |  | 
|  | return rc; | 
|  | }; | 
|  |  | 
|  | /* attribute for HRT in sysfs */ | 
|  | static struct bin_attribute i2o_config_hrt_attr = { | 
|  | .attr = { | 
|  | .name = "hrt", | 
|  | .mode = S_IRUGO, | 
|  | .owner = THIS_MODULE}, | 
|  | .size = 0, | 
|  | .read = i2o_config_read_hrt | 
|  | }; | 
|  |  | 
|  | /* attribute for LCT in sysfs */ | 
|  | static struct bin_attribute i2o_config_lct_attr = { | 
|  | .attr = { | 
|  | .name = "lct", | 
|  | .mode = S_IRUGO, | 
|  | .owner = THIS_MODULE}, | 
|  | .size = 0, | 
|  | .read = i2o_config_read_lct | 
|  | }; | 
|  |  | 
|  | /* IRTOS firmware access */ | 
|  | I2O_CONFIG_SW_ATTR(irtos, S_IWRSR, I2O_SOFTWARE_MODULE_IRTOS, 0); | 
|  |  | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  |  | 
|  | /* | 
|  | * attribute for BIOS / SMOR, nvram and serial number access on DPT / Adaptec | 
|  | * controllers | 
|  | */ | 
|  | I2O_CONFIG_SW_ATTR(bios, S_IWRSR, I2O_SOFTWARE_MODULE_IOP_PRIVATE, 0); | 
|  | I2O_CONFIG_SW_ATTR(nvram, S_IWRSR, I2O_SOFTWARE_MODULE_IOP_CONFIG, 0); | 
|  | I2O_CONFIG_SW_ATTR(serial, S_IWRSR, I2O_SOFTWARE_MODULE_IOP_CONFIG, 1); | 
|  |  | 
|  | #endif | 
|  |  | 
|  | /** | 
|  | *	i2o_config_notify_controller_add - Notify of added controller | 
|  | *	@c: the controller which was added | 
|  | * | 
|  | *	If a I2O controller is added, we catch the notification to add sysfs | 
|  | *	entries. | 
|  | */ | 
|  | static void i2o_config_notify_controller_add(struct i2o_controller *c) | 
|  | { | 
|  | struct kobject *kobj = &c->exec->device.kobj; | 
|  |  | 
|  | sysfs_create_bin_file(kobj, &i2o_config_hrt_attr); | 
|  | sysfs_create_bin_file(kobj, &i2o_config_lct_attr); | 
|  |  | 
|  | sysfs_create_fops_file(kobj, &i2o_config_attr_irtos); | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  | if (c->adaptec) { | 
|  | sysfs_create_fops_file(kobj, &i2o_config_attr_bios); | 
|  | sysfs_create_fops_file(kobj, &i2o_config_attr_nvram); | 
|  | sysfs_create_fops_file(kobj, &i2o_config_attr_serial); | 
|  | } | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | /** | 
|  | *	i2o_config_notify_controller_remove - Notify of removed controller | 
|  | *	@c: the controller which was removed | 
|  | * | 
|  | *	If a I2O controller is removed, we catch the notification to remove the | 
|  | *	sysfs entries. | 
|  | */ | 
|  | static void i2o_config_notify_controller_remove(struct i2o_controller *c) | 
|  | { | 
|  | struct kobject *kobj = &c->exec->device.kobj; | 
|  |  | 
|  | #ifdef CONFIG_I2O_EXT_ADAPTEC | 
|  | if (c->adaptec) { | 
|  | sysfs_remove_fops_file(kobj, &i2o_config_attr_serial); | 
|  | sysfs_remove_fops_file(kobj, &i2o_config_attr_nvram); | 
|  | sysfs_remove_fops_file(kobj, &i2o_config_attr_bios); | 
|  | } | 
|  | #endif | 
|  | sysfs_remove_fops_file(kobj, &i2o_config_attr_irtos); | 
|  |  | 
|  | sysfs_remove_bin_file(kobj, &i2o_config_lct_attr); | 
|  | sysfs_remove_bin_file(kobj, &i2o_config_hrt_attr); | 
|  | }; | 
|  |  | 
|  | /* Config OSM driver struct */ | 
|  | static struct i2o_driver i2o_config_driver = { | 
|  | .name = OSM_NAME, | 
|  | .notify_controller_add = i2o_config_notify_controller_add, | 
|  | .notify_controller_remove = i2o_config_notify_controller_remove | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_I2O_CONFIG_OLD_IOCTL | 
|  | #include "i2o_config.c" | 
|  | #endif | 
|  |  | 
|  | /** | 
|  | *	i2o_config_init - Configuration OSM initialization function | 
|  | * | 
|  | *	Registers Configuration OSM in the I2O core and if old ioctl's are | 
|  | *	compiled in initialize them. | 
|  | * | 
|  | *	Returns 0 on success or negative error code on failure. | 
|  | */ | 
|  | static int __init i2o_config_init(void) | 
|  | { | 
|  | printk(KERN_INFO OSM_DESCRIPTION " v" OSM_VERSION "\n"); | 
|  |  | 
|  | if (i2o_driver_register(&i2o_config_driver)) { | 
|  | osm_err("handler register failed.\n"); | 
|  | return -EBUSY; | 
|  | } | 
|  | #ifdef CONFIG_I2O_CONFIG_OLD_IOCTL | 
|  | if (i2o_config_old_init()) | 
|  | i2o_driver_unregister(&i2o_config_driver); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | *	i2o_config_exit - Configuration OSM exit function | 
|  | * | 
|  | *	If old ioctl's are compiled in exit remove them and unregisters | 
|  | *	Configuration OSM from I2O core. | 
|  | */ | 
|  | static void i2o_config_exit(void) | 
|  | { | 
|  | #ifdef CONFIG_I2O_CONFIG_OLD_IOCTL | 
|  | i2o_config_old_exit(); | 
|  | #endif | 
|  |  | 
|  | i2o_driver_unregister(&i2o_config_driver); | 
|  | } | 
|  |  | 
|  | MODULE_AUTHOR("Markus Lidel <Markus.Lidel@shadowconnect.com>"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_DESCRIPTION(OSM_DESCRIPTION); | 
|  | MODULE_VERSION(OSM_VERSION); | 
|  |  | 
|  | module_init(i2o_config_init); | 
|  | module_exit(i2o_config_exit); |