enhance device info matching for multiple tables

The current scsi_devinfo.c matching routines use a single table for
the global blacklist.  However, we're developing a need to blacklist
from specific transports too (notably some tape drives using SPI which
don't respond well to high speed protocols).  Instead of developing
separate blacklist matching for each transport class needing it,
enhance the current list matching to permit multiple lists.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
This commit is contained in:
James Bottomley 2009-06-17 15:01:58 -04:00
parent e0420029de
commit 598fa4b775
2 changed files with 240 additions and 22 deletions

View File

@ -24,6 +24,13 @@ struct scsi_dev_info_list {
unsigned compatible; /* for use with scsi_static_device_list entries */ 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 const char spaces[] = " "; /* 16 of them */
static unsigned scsi_default_dev_flags; static unsigned scsi_default_dev_flags;
@ -247,6 +254,22 @@ static struct {
{ NULL, NULL, NULL, 0 }, { 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 * scsi_strcpy_devinfo: called from scsi_dev_info_list_add to copy into
* devinfo vendor and model strings. * 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, static int scsi_dev_info_list_add(int compatible, char *vendor, char *model,
char *strflags, int flags) 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 *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); devinfo = kmalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) { if (!devinfo) {
@ -317,12 +371,15 @@ static int scsi_dev_info_list_add(int compatible, char *vendor, char *model,
devinfo->compatible = compatible; devinfo->compatible = compatible;
if (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 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; 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. * 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 * @model: model name
* *
* Description: * Description:
* Search the scsi_dev_info_list for an entry matching @vendor and * Search the global scsi_dev_info_list (specified by list zero)
* @model, if found, return the matching flags value, else return * for an entry matching @vendor and @model, if found, return the
* the host or global default settings. Called during scan time. * matching flags value, else return the host or global default
* settings. Called during scan time.
**/ **/
int scsi_get_device_flags(struct scsi_device *sdev, int scsi_get_device_flags(struct scsi_device *sdev,
const unsigned char *vendor, const unsigned char *vendor,
const unsigned char *model) 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; struct scsi_dev_info_list *devinfo;
unsigned int bflags; struct scsi_dev_info_list_table *devinfo_table;
bflags = sdev->sdev_bflags; devinfo_table = scsi_devinfo_lookup_by_key(key);
if (!bflags)
bflags = scsi_default_dev_flags;
list_for_each_entry(devinfo, &scsi_dev_info_list, dev_info_list) { if (IS_ERR(devinfo_table))
return PTR_ERR(devinfo_table);
list_for_each_entry(devinfo, &devinfo_table->scsi_dev_info_list,
dev_info_list) {
if (devinfo->compatible) { if (devinfo->compatible) {
/* /*
* Behave like the older version of get_device_flags. * 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 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 #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) 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 = 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", seq_printf(m, "'%.8s' '%.16s' 0x%x\n",
devinfo->vendor, devinfo->model, devinfo->flags); devinfo->vendor, devinfo->model, devinfo->flags);
return 0; 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) static void devinfo_seq_stop(struct seq_file *m, void *v)
{ {
kfree(v);
} }
static const struct seq_operations scsi_devinfo_seq_ops = { static const struct seq_operations scsi_devinfo_seq_ops = {
@ -549,19 +689,78 @@ MODULE_PARM_DESC(default_dev_flags,
**/ **/
void scsi_exit_devinfo(void) void scsi_exit_devinfo(void)
{ {
struct list_head *lh, *lh_next;
struct scsi_dev_info_list *devinfo;
#ifdef CONFIG_SCSI_PROC_FS #ifdef CONFIG_SCSI_PROC_FS
remove_proc_entry("scsi/device_info", NULL); remove_proc_entry("scsi/device_info", NULL);
#endif #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, devinfo = list_entry(lh, struct scsi_dev_info_list,
dev_info_list); dev_info_list);
kfree(devinfo); kfree(devinfo);
} }
kfree(devinfo_table);
return 0;
} }
EXPORT_SYMBOL(scsi_dev_info_remove_list);
/** /**
* scsi_init_devinfo - set up the dynamic device list. * scsi_init_devinfo - set up the dynamic device list.
@ -577,10 +776,14 @@ int __init scsi_init_devinfo(void)
#endif #endif
int error, i; 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) if (error)
return 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++) { for (i = 0; scsi_static_device_list[i].vendor; i++) {
error = scsi_dev_info_list_add(1 /* compatibile */, error = scsi_dev_info_list_add(1 /* compatibile */,
scsi_static_device_list[i].vendor, scsi_static_device_list[i].vendor,

View File

@ -39,9 +39,24 @@ static inline void scsi_log_completion(struct scsi_cmnd *cmd, int disposition)
#endif #endif
/* scsi_devinfo.c */ /* scsi_devinfo.c */
/* list of keys for the lists */
enum {
SCSI_DEVINFO_GLOBAL = 0,
};
extern int scsi_get_device_flags(struct scsi_device *sdev, extern int scsi_get_device_flags(struct scsi_device *sdev,
const unsigned char *vendor, const unsigned char *vendor,
const unsigned char *model); const unsigned char *model);
extern int scsi_get_device_flags_keyed(struct scsi_device *sdev,
const unsigned char *vendor,
const unsigned char *model, int key);
extern int scsi_dev_info_list_add_keyed(int compatible, char *vendor,
char *model, char *strflags,
int flags, int key);
extern int scsi_dev_info_add_list(int key, const char *name);
extern int scsi_dev_info_remove_list(int key);
extern int __init scsi_init_devinfo(void); extern int __init scsi_init_devinfo(void);
extern void scsi_exit_devinfo(void); extern void scsi_exit_devinfo(void);