|
@@ -24,6 +24,13 @@ struct scsi_dev_info_list {
|
|
|
unsigned compatible; /* for use with scsi_static_device_list entries */
|
|
|
};
|
|
|
|
|
|
+struct scsi_dev_info_list_table {
|
|
|
+ struct list_head node; /* our node for being on the master list */
|
|
|
+ struct list_head scsi_dev_info_list; /* head of dev info list */
|
|
|
+ const char *name; /* name of list for /proc (NULL for global) */
|
|
|
+ int key; /* unique numeric identifier */
|
|
|
+};
|
|
|
+
|
|
|
|
|
|
static const char spaces[] = " "; /* 16 of them */
|
|
|
static unsigned scsi_default_dev_flags;
|
|
@@ -247,6 +254,22 @@ static struct {
|
|
|
{ NULL, NULL, NULL, 0 },
|
|
|
};
|
|
|
|
|
|
+static struct scsi_dev_info_list_table *scsi_devinfo_lookup_by_key(int key)
|
|
|
+{
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table;
|
|
|
+ int found = 0;
|
|
|
+
|
|
|
+ list_for_each_entry(devinfo_table, &scsi_dev_info_list, node)
|
|
|
+ if (devinfo_table->key == key) {
|
|
|
+ found = 1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!found)
|
|
|
+ return ERR_PTR(-EINVAL);
|
|
|
+
|
|
|
+ return devinfo_table;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* scsi_strcpy_devinfo: called from scsi_dev_info_list_add to copy into
|
|
|
* devinfo vendor and model strings.
|
|
@@ -295,8 +318,39 @@ static void scsi_strcpy_devinfo(char *name, char *to, size_t to_length,
|
|
|
**/
|
|
|
static int scsi_dev_info_list_add(int compatible, char *vendor, char *model,
|
|
|
char *strflags, int flags)
|
|
|
+{
|
|
|
+ return scsi_dev_info_list_add_keyed(compatible, vendor, model,
|
|
|
+ strflags, flags,
|
|
|
+ SCSI_DEVINFO_GLOBAL);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * scsi_dev_info_list_add_keyed - add one dev_info list entry.
|
|
|
+ * @compatible: if true, null terminate short strings. Otherwise space pad.
|
|
|
+ * @vendor: vendor string
|
|
|
+ * @model: model (product) string
|
|
|
+ * @strflags: integer string
|
|
|
+ * @flags: if strflags NULL, use this flag value
|
|
|
+ * @key: specify list to use
|
|
|
+ *
|
|
|
+ * Description:
|
|
|
+ * Create and add one dev_info entry for @vendor, @model,
|
|
|
+ * @strflags or @flag in list specified by @key. If @compatible,
|
|
|
+ * add to the tail of the list, do not space pad, and set
|
|
|
+ * devinfo->compatible. The scsi_static_device_list entries are
|
|
|
+ * added with @compatible 1 and @clfags NULL.
|
|
|
+ *
|
|
|
+ * Returns: 0 OK, -error on failure.
|
|
|
+ **/
|
|
|
+int scsi_dev_info_list_add_keyed(int compatible, char *vendor, char *model,
|
|
|
+ char *strflags, int flags, int key)
|
|
|
{
|
|
|
struct scsi_dev_info_list *devinfo;
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table =
|
|
|
+ scsi_devinfo_lookup_by_key(key);
|
|
|
+
|
|
|
+ if (IS_ERR(devinfo_table))
|
|
|
+ return PTR_ERR(devinfo_table);
|
|
|
|
|
|
devinfo = kmalloc(sizeof(*devinfo), GFP_KERNEL);
|
|
|
if (!devinfo) {
|
|
@@ -317,12 +371,15 @@ static int scsi_dev_info_list_add(int compatible, char *vendor, char *model,
|
|
|
devinfo->compatible = compatible;
|
|
|
|
|
|
if (compatible)
|
|
|
- list_add_tail(&devinfo->dev_info_list, &scsi_dev_info_list);
|
|
|
+ list_add_tail(&devinfo->dev_info_list,
|
|
|
+ &devinfo_table->scsi_dev_info_list);
|
|
|
else
|
|
|
- list_add(&devinfo->dev_info_list, &scsi_dev_info_list);
|
|
|
+ list_add(&devinfo->dev_info_list,
|
|
|
+ &devinfo_table->scsi_dev_info_list);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
+EXPORT_SYMBOL(scsi_dev_info_list_add_keyed);
|
|
|
|
|
|
/**
|
|
|
* scsi_dev_info_list_add_str - parse dev_list and add to the scsi_dev_info_list.
|
|
@@ -382,22 +439,48 @@ static int scsi_dev_info_list_add_str(char *dev_list)
|
|
|
* @model: model name
|
|
|
*
|
|
|
* Description:
|
|
|
- * Search the scsi_dev_info_list for an entry matching @vendor and
|
|
|
- * @model, if found, return the matching flags value, else return
|
|
|
- * the host or global default settings. Called during scan time.
|
|
|
+ * Search the global scsi_dev_info_list (specified by list zero)
|
|
|
+ * for an entry matching @vendor and @model, if found, return the
|
|
|
+ * matching flags value, else return the host or global default
|
|
|
+ * settings. Called during scan time.
|
|
|
**/
|
|
|
int scsi_get_device_flags(struct scsi_device *sdev,
|
|
|
const unsigned char *vendor,
|
|
|
const unsigned char *model)
|
|
|
+{
|
|
|
+ return scsi_get_device_flags_keyed(sdev, vendor, model,
|
|
|
+ SCSI_DEVINFO_GLOBAL);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * get_device_flags_keyed - get device specific flags from the dynamic device list.
|
|
|
+ * @sdev: &scsi_device to get flags for
|
|
|
+ * @vendor: vendor name
|
|
|
+ * @model: model name
|
|
|
+ * @key: list to look up
|
|
|
+ *
|
|
|
+ * Description:
|
|
|
+ * Search the scsi_dev_info_list specified by @key for an entry
|
|
|
+ * matching @vendor and @model, if found, return the matching
|
|
|
+ * flags value, else return the host or global default settings.
|
|
|
+ * Called during scan time.
|
|
|
+ **/
|
|
|
+int scsi_get_device_flags_keyed(struct scsi_device *sdev,
|
|
|
+ const unsigned char *vendor,
|
|
|
+ const unsigned char *model,
|
|
|
+ int key)
|
|
|
{
|
|
|
struct scsi_dev_info_list *devinfo;
|
|
|
- unsigned int bflags;
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table;
|
|
|
+
|
|
|
+ devinfo_table = scsi_devinfo_lookup_by_key(key);
|
|
|
|
|
|
- bflags = sdev->sdev_bflags;
|
|
|
- if (!bflags)
|
|
|
- bflags = scsi_default_dev_flags;
|
|
|
+ if (IS_ERR(devinfo_table))
|
|
|
+ return PTR_ERR(devinfo_table);
|
|
|
|
|
|
- list_for_each_entry(devinfo, &scsi_dev_info_list, dev_info_list) {
|
|
|
+ list_for_each_entry(devinfo, &devinfo_table->scsi_dev_info_list,
|
|
|
+ dev_info_list) {
|
|
|
if (devinfo->compatible) {
|
|
|
/*
|
|
|
* Behave like the older version of get_device_flags.
|
|
@@ -447,32 +530,89 @@ int scsi_get_device_flags(struct scsi_device *sdev,
|
|
|
return devinfo->flags;
|
|
|
}
|
|
|
}
|
|
|
- return bflags;
|
|
|
+ /* nothing found, return nothing */
|
|
|
+ if (key != SCSI_DEVINFO_GLOBAL)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /* except for the global list, where we have an exception */
|
|
|
+ if (sdev->sdev_bflags)
|
|
|
+ return sdev->sdev_bflags;
|
|
|
+
|
|
|
+ return scsi_default_dev_flags;
|
|
|
}
|
|
|
+EXPORT_SYMBOL(scsi_get_device_flags_keyed);
|
|
|
|
|
|
#ifdef CONFIG_SCSI_PROC_FS
|
|
|
+struct double_list {
|
|
|
+ struct list_head *top;
|
|
|
+ struct list_head *bottom;
|
|
|
+};
|
|
|
+
|
|
|
static int devinfo_seq_show(struct seq_file *m, void *v)
|
|
|
{
|
|
|
+ struct double_list *dl = v;
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table =
|
|
|
+ list_entry(dl->top, struct scsi_dev_info_list_table, node);
|
|
|
struct scsi_dev_info_list *devinfo =
|
|
|
- list_entry(v, struct scsi_dev_info_list, dev_info_list);
|
|
|
+ list_entry(dl->bottom, struct scsi_dev_info_list,
|
|
|
+ dev_info_list);
|
|
|
+
|
|
|
+ if (devinfo_table->scsi_dev_info_list.next == dl->bottom &&
|
|
|
+ devinfo_table->name)
|
|
|
+ seq_printf(m, "[%s]:\n", devinfo_table->name);
|
|
|
|
|
|
seq_printf(m, "'%.8s' '%.16s' 0x%x\n",
|
|
|
- devinfo->vendor, devinfo->model, devinfo->flags);
|
|
|
+ devinfo->vendor, devinfo->model, devinfo->flags);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static void * devinfo_seq_start(struct seq_file *m, loff_t *pos)
|
|
|
+static void *devinfo_seq_start(struct seq_file *m, loff_t *ppos)
|
|
|
{
|
|
|
- return seq_list_start(&scsi_dev_info_list, *pos);
|
|
|
+ struct double_list *dl = kmalloc(sizeof(*dl), GFP_KERNEL);
|
|
|
+ loff_t pos = *ppos;
|
|
|
+
|
|
|
+ if (!dl)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ list_for_each(dl->top, &scsi_dev_info_list) {
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table =
|
|
|
+ list_entry(dl->top, struct scsi_dev_info_list_table,
|
|
|
+ node);
|
|
|
+ list_for_each(dl->bottom, &devinfo_table->scsi_dev_info_list)
|
|
|
+ if (pos-- == 0)
|
|
|
+ return dl;
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(dl);
|
|
|
+ return NULL;
|
|
|
}
|
|
|
|
|
|
-static void * devinfo_seq_next(struct seq_file *m, void *v, loff_t *pos)
|
|
|
+static void *devinfo_seq_next(struct seq_file *m, void *v, loff_t *ppos)
|
|
|
{
|
|
|
- return seq_list_next(v, &scsi_dev_info_list, pos);
|
|
|
+ struct double_list *dl = v;
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table =
|
|
|
+ list_entry(dl->top, struct scsi_dev_info_list_table, node);
|
|
|
+
|
|
|
+ ++*ppos;
|
|
|
+ dl->bottom = dl->bottom->next;
|
|
|
+ while (&devinfo_table->scsi_dev_info_list == dl->bottom) {
|
|
|
+ dl->top = dl->top->next;
|
|
|
+ if (dl->top == &scsi_dev_info_list) {
|
|
|
+ kfree(dl);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ devinfo_table = list_entry(dl->top,
|
|
|
+ struct scsi_dev_info_list_table,
|
|
|
+ node);
|
|
|
+ dl->bottom = devinfo_table->scsi_dev_info_list.next;
|
|
|
+ }
|
|
|
+
|
|
|
+ return dl;
|
|
|
}
|
|
|
|
|
|
static void devinfo_seq_stop(struct seq_file *m, void *v)
|
|
|
{
|
|
|
+ kfree(v);
|
|
|
}
|
|
|
|
|
|
static const struct seq_operations scsi_devinfo_seq_ops = {
|
|
@@ -549,19 +689,78 @@ MODULE_PARM_DESC(default_dev_flags,
|
|
|
**/
|
|
|
void scsi_exit_devinfo(void)
|
|
|
{
|
|
|
- struct list_head *lh, *lh_next;
|
|
|
- struct scsi_dev_info_list *devinfo;
|
|
|
-
|
|
|
#ifdef CONFIG_SCSI_PROC_FS
|
|
|
remove_proc_entry("scsi/device_info", NULL);
|
|
|
#endif
|
|
|
|
|
|
- list_for_each_safe(lh, lh_next, &scsi_dev_info_list) {
|
|
|
+ scsi_dev_info_remove_list(SCSI_DEVINFO_GLOBAL);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * scsi_dev_info_add_list - add a new devinfo list
|
|
|
+ * @key: key of the list to add
|
|
|
+ * @name: Name of the list to add (for /proc/scsi/device_info)
|
|
|
+ *
|
|
|
+ * Adds the requested list, returns zero on success, -EEXIST if the
|
|
|
+ * key is already registered to a list, or other error on failure.
|
|
|
+ */
|
|
|
+int scsi_dev_info_add_list(int key, const char *name)
|
|
|
+{
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table =
|
|
|
+ scsi_devinfo_lookup_by_key(key);
|
|
|
+
|
|
|
+ if (!IS_ERR(devinfo_table))
|
|
|
+ /* list already exists */
|
|
|
+ return -EEXIST;
|
|
|
+
|
|
|
+ devinfo_table = kmalloc(sizeof(*devinfo_table), GFP_KERNEL);
|
|
|
+
|
|
|
+ if (!devinfo_table)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ INIT_LIST_HEAD(&devinfo_table->node);
|
|
|
+ INIT_LIST_HEAD(&devinfo_table->scsi_dev_info_list);
|
|
|
+ devinfo_table->name = name;
|
|
|
+ devinfo_table->key = key;
|
|
|
+ list_add_tail(&devinfo_table->node, &scsi_dev_info_list);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(scsi_dev_info_add_list);
|
|
|
+
|
|
|
+/**
|
|
|
+ * scsi_dev_info_remove_list - destroy an added devinfo list
|
|
|
+ * @key: key of the list to destroy
|
|
|
+ *
|
|
|
+ * Iterates over the entire list first, freeing all the values, then
|
|
|
+ * frees the list itself. Returns 0 on success or -EINVAL if the key
|
|
|
+ * can't be found.
|
|
|
+ */
|
|
|
+int scsi_dev_info_remove_list(int key)
|
|
|
+{
|
|
|
+ struct list_head *lh, *lh_next;
|
|
|
+ struct scsi_dev_info_list_table *devinfo_table =
|
|
|
+ scsi_devinfo_lookup_by_key(key);
|
|
|
+
|
|
|
+ if (IS_ERR(devinfo_table))
|
|
|
+ /* no such list */
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* remove from the master list */
|
|
|
+ list_del(&devinfo_table->node);
|
|
|
+
|
|
|
+ list_for_each_safe(lh, lh_next, &devinfo_table->scsi_dev_info_list) {
|
|
|
+ struct scsi_dev_info_list *devinfo;
|
|
|
+
|
|
|
devinfo = list_entry(lh, struct scsi_dev_info_list,
|
|
|
dev_info_list);
|
|
|
kfree(devinfo);
|
|
|
}
|
|
|
+ kfree(devinfo_table);
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
+EXPORT_SYMBOL(scsi_dev_info_remove_list);
|
|
|
|
|
|
/**
|
|
|
* scsi_init_devinfo - set up the dynamic device list.
|
|
@@ -577,10 +776,14 @@ int __init scsi_init_devinfo(void)
|
|
|
#endif
|
|
|
int error, i;
|
|
|
|
|
|
- error = scsi_dev_info_list_add_str(scsi_dev_flags);
|
|
|
+ error = scsi_dev_info_add_list(SCSI_DEVINFO_GLOBAL, NULL);
|
|
|
if (error)
|
|
|
return error;
|
|
|
|
|
|
+ error = scsi_dev_info_list_add_str(scsi_dev_flags);
|
|
|
+ if (error)
|
|
|
+ goto out;
|
|
|
+
|
|
|
for (i = 0; scsi_static_device_list[i].vendor; i++) {
|
|
|
error = scsi_dev_info_list_add(1 /* compatibile */,
|
|
|
scsi_static_device_list[i].vendor,
|