Commit bb7b9e76 authored by Patrick Mansfield's avatar Patrick Mansfield Committed by James Bottomley

dynamic device info flag entries

This patch allows scsi device flags like those in the current scsi_scan.c
device_list to be added via the command line or via /proc/scsi/device_info.

It also allows a default flag to be set via the command line.

This should allow for the (eventual) removal of the current device_list.

Example boot command line arguments:

scsi_default_dev_flags=0x1 scsi_dev_flags="IBM:LN V1.2Rack:0x200"

The above means that a SCSI device with vendor "IBM     ", model
(product) "LN V1.2Rack     " will get a flag of 0x200 during scanning,
meaning it should be treated like a SCSI-3 device for scanning. All
other devices without an entry would get the default flag value of 1,
so they would not be scanned past LUN 0 (the same behaviour as 
max_scsi_luns = 1).

I left the old device list and BLIST defines in scsi_scan.c.

I could not get lilo to allow two spaces, so I could not use this
with my device that has model "ST318203LC    !#". I didn't try grub.
parent df860b3d
......@@ -163,6 +163,10 @@ const char *const scsi_device_types[MAX_SCSI_DEVICE_CODE] =
};
static char * scsi_null_device_strs = "nullnullnullnull";
static const char * const spaces = " "; /* 16 of them */
static unsigned scsi_default_dev_flags;
static LIST_HEAD(scsi_dev_info_list);
/*
* Function prototypes.
......@@ -1670,7 +1674,312 @@ void scsi_adjust_queue_depth(Scsi_Device *SDpnt, int tagged, int tags)
spin_unlock_irqrestore(&device_request_lock, flags);
}
/*
* scsi_strcpy_devinfo: called from scsi_dev_info_list_add to copy into
* devinfo vendor and model strings.
*/
static void scsi_strcpy_devinfo(char *name, char *to, size_t to_length,
char *from, int compatible)
{
size_t from_length;
from_length = strlen(from);
strncpy(to, from, min(to_length, from_length));
if (from_length < to_length)
if (compatible) {
/*
* NUL terminate the string if it is short.
*/
to[from_length] = '\0';
} else {
/*
* space pad the string if it is short.
*/
strncpy(&to[from_length], spaces,
to_length - from_length);
}
if (from_length > to_length)
printk(KERN_WARNING "%s: %s string '%s' is too long\n",
__FUNCTION__, name, from);
}
/**
* scsi_dev_info_list_add: add one dev_info list entry.
* @vendor: vendor string
* @model: model (product) string
* @strflags: integer string
* @flag: if strflags NULL, use this flag value
*
* Description:
* Create and add one dev_info entry for @vendor, @model, @strflags or
* @flag. 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.
**/
static int scsi_dev_info_list_add(int compatible, char *vendor, char *model,
char *strflags, int flags)
{
struct scsi_dev_info_list *devinfo;
devinfo = kmalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
printk(KERN_ERR "%s: no memory\n", __FUNCTION__);
return -ENOMEM;
}
scsi_strcpy_devinfo("vendor", devinfo->vendor, sizeof(devinfo->vendor),
vendor, compatible);
scsi_strcpy_devinfo("model", devinfo->model, sizeof(devinfo->model),
model, compatible);
if (strflags)
devinfo->flags = simple_strtoul(strflags, NULL, 0);
else
devinfo->flags = flags;
devinfo->compatible = compatible;
if (compatible)
list_add_tail(&devinfo->dev_info_list, &scsi_dev_info_list);
else
list_add(&devinfo->dev_info_list, &scsi_dev_info_list);
return 0;
}
/**
* scsi_dev_info_list_add_str: parse dev_list and add to the
* scsi_dev_info_list.
* @dev_list: string of device flags to add
*
* Description:
* Parse dev_list, and add entries to the scsi_dev_info_list.
* dev_list is of the form "vendor:product:flag,vendor:product:flag".
* dev_list is modified via strsep. Can be called for command line
* addition, for proc or mabye a sysfs interface.
*
* Returns: 0 if OK, -error on failure.
**/
static int scsi_dev_info_list_add_str (char *dev_list)
{
char *vendor, *model, *strflags, *next;
char *next_check;
int res = 0;
next = dev_list;
if (next && next[0] == '"') {
/*
* Ignore both the leading and trailing quote.
*/
next++;
next_check = ",\"";
} else {
next_check = ",";
}
/*
* For the leading and trailing '"' case, the for loop comes
* through the last time with vendor[0] == '\0'.
*/
for (vendor = strsep(&next, ":"); vendor && (vendor[0] != '\0')
&& (res == 0); vendor = strsep(&next, ":")) {
strflags = NULL;
model = strsep(&next, ":");
if (model)
strflags = strsep(&next, next_check);
if (!model || !strflags) {
printk(KERN_ERR "%s: bad dev info string '%s' '%s'"
" '%s'\n", __FUNCTION__, vendor, model,
strflags);
res = -EINVAL;
} else
res = scsi_dev_info_list_add(0 /* compatible */, vendor,
model, strflags, 0);
}
return res;
}
/**
* scsi_dev_list_init: set up the dynamic device list.
* @dev_list: string of device flags to add
*
* Description:
* Add command line @dev_list entries, then add
* scsi_static_device_list entries to the scsi device info list.
**/
static void scsi_dev_info_list_init (char *dev_list)
{
int i;
if (scsi_dev_info_list_add_str(dev_list) == -ENOMEM)
return;
for (i = 0; scsi_static_device_list[i].vendor != NULL; i++)
if (scsi_dev_info_list_add(1 /* compatibile */,
scsi_static_device_list[i].vendor,
scsi_static_device_list[i].model,
NULL,
scsi_static_device_list[i].flags) == -ENOMEM)
return;
}
/**
* scsi_dev_info_list_delete: called from scsi.c:exit_scsi to remove
* the scsi_dev_info_list.
**/
static void scsi_dev_info_list_delete (void)
{
struct list_head *lh, *lh_next;
struct scsi_dev_info_list *devinfo;
list_for_each_safe(lh, lh_next, &scsi_dev_info_list) {
devinfo = list_entry(lh, struct scsi_dev_info_list,
dev_info_list);
kfree(devinfo);
}
}
/**
* get_device_flags - get device specific flags from the dynamic device
* list. Called during scan time.
* @vendor: vendor name
* @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
* scsi_default_dev_flags.
**/
int scsi_get_device_flags(unsigned char *vendor, unsigned char *model)
{
struct scsi_dev_info_list *devinfo;
list_for_each_entry(devinfo, &scsi_dev_info_list, dev_info_list) {
if (devinfo->compatible) {
/*
* Behave like the older version of get_device_flags.
*/
size_t max;
/*
* XXX why skip leading spaces? If an odd INQUIRY
* value, that should have been part of the
* scsi_static_device_list[] entry, such as " FOO"
* rather than "FOO". Since this code is already
* here, and we don't know what device it is
* trying to work with, leave it as-is.
*/
max = 8; /* max length of vendor */
while ((max > 0) && *vendor == ' ') {
max--;
vendor++;
}
/*
* XXX removing the following strlen() would be
* good, using it means that for a an entry not in
* the list, we scan every byte of every vendor
* listed in scsi_static_device_list[], and never match
* a single one (and still have to compare at
* least the first byte of each vendor).
*/
if (memcmp(devinfo->vendor, vendor,
min(max, strlen(devinfo->vendor))))
continue;
/*
* Skip spaces again.
*/
max = 16; /* max length of model */
while ((max > 0) && *model == ' ') {
max--;
model++;
}
if (memcmp(devinfo->model, model,
min(max, strlen(devinfo->model))))
continue;
return devinfo->flags;
} else {
if (!memcmp(devinfo->vendor, vendor,
sizeof(devinfo->vendor)) &&
!memcmp(devinfo->model, model,
sizeof(devinfo->model)))
return devinfo->flags;
}
}
return scsi_default_dev_flags;
}
#ifdef CONFIG_PROC_FS
/*
* proc_scsi_dev_info_read: dump the scsi_dev_info_list via
* /proc/scsi/device_info
*/
static int proc_scsi_dev_info_read(char *buffer, char **start, off_t offset,
int length)
{
struct scsi_dev_info_list *devinfo;
int size, len = 0;
off_t begin = 0;
off_t pos = 0;
list_for_each_entry(devinfo, &scsi_dev_info_list, dev_info_list) {
size = sprintf(buffer + len, "'%.8s' '%.16s' 0x%x\n",
devinfo->vendor, devinfo->model, devinfo->flags);
len += size;
pos = begin + len;
if (pos < offset) {
len = 0;
begin = pos;
}
if (pos > offset + length)
goto stop_output;
}
stop_output:
*start = buffer + (offset - begin); /* Start of wanted data */
len -= (offset - begin); /* Start slop */
if (len > length)
len = length; /* Ending slop */
return (len);
}
/*
* proc_scsi_dev_info_write: allow additions to the scsi_dev_info_list via
* /proc.
*
* Use: echo "vendor:model:flag" > /proc/scsi/device_info
*
* To add a black/white list entry for vendor and model with an integer
* value of flag to the scsi device info list.
*/
static int proc_scsi_dev_info_write (struct file * file, const char * buf,
unsigned long length, void *data)
{
char *buffer;
int err = length;
if (!buf || length>PAGE_SIZE)
return -EINVAL;
if (!(buffer = (char *) __get_free_page(GFP_KERNEL)))
return -ENOMEM;
if(copy_from_user(buffer, buf, length)) {
err =-EFAULT;
goto out;
}
if (length < PAGE_SIZE)
buffer[length] = '\0';
else if (buffer[PAGE_SIZE-1]) {
err = -EINVAL;
goto out;
}
scsi_dev_info_list_add_str(buffer);
out:
free_page((unsigned long) buffer);
return err;
}
static int scsi_proc_info(char *buffer, char **start, off_t offset, int length)
{
Scsi_Device *scd;
......@@ -2242,8 +2551,17 @@ static void scsi_dump_status(int level)
#endif /* CONFIG_PROC_FS */
static char *scsihosts;
static char *scsi_dev_flags;
MODULE_PARM(scsihosts, "s");
MODULE_PARM(scsi_dev_flags, "s");
MODULE_PARM_DESC(scsi_dev_flags,
"Given scsi_dev_flags=vendor:model:flags, add a black/white list"
" entry for vendor and model with an integer value of flags"
" to the scsi device info list");
MODULE_PARM(scsi_default_dev_flags, "i");
MODULE_PARM_DESC(scsi_default_dev_flags,
"scsi default device flag integer value");
MODULE_DESCRIPTION("SCSI core");
MODULE_LICENSE("GPL");
......@@ -2255,6 +2573,30 @@ int __init scsi_setup(char *str)
}
__setup("scsihosts=", scsi_setup);
int __init setup_scsi_dev_flags(char *str)
{
scsi_dev_flags = str;
return 1;
}
__setup("scsi_dev_flags=", setup_scsi_dev_flags);
static int __init setup_scsi_default_dev_flags(char *str)
{
unsigned int tmp;
if (get_option(&str, &tmp) == 1) {
scsi_default_dev_flags = tmp;
printk(KERN_WARNING "%s %d\n", __FUNCTION__,
scsi_default_dev_flags);
return 1;
} else {
printk(KERN_WARNING "%s: usage scsi_default_dev_flags=intr\n",
__FUNCTION__);
return 0;
}
}
__setup("scsi_default_dev_flags=", setup_scsi_default_dev_flags);
#endif
static void *scsi_pool_alloc(int gfp_mask, void *data)
......@@ -2377,12 +2719,24 @@ static int __init init_scsi(void)
return -ENOMEM;
}
generic->write_proc = proc_scsi_gen_write;
generic = create_proc_info_entry ("scsi/device_info", 0, 0,
proc_scsi_dev_info_read);
if (!generic) {
printk (KERN_ERR "cannot init /proc/scsi/device_info\n");
remove_proc_entry("scsi/scsi", 0);
remove_proc_entry("scsi", 0);
return -ENOMEM;
}
generic->write_proc = proc_scsi_dev_info_write;
#endif
scsi_devfs_handle = devfs_mk_dir (NULL, "scsi", NULL);
scsi_host_hn_init(scsihosts);
scsi_dev_info_list_init(scsi_dev_flags);
bus_register(&scsi_driverfs_bus_type);
/* Where we handle work queued by scsi_done */
......@@ -2397,10 +2751,13 @@ static void __exit exit_scsi(void)
devfs_unregister (scsi_devfs_handle);
scsi_dev_info_list_delete();
scsi_host_hn_release();
#ifdef CONFIG_PROC_FS
/* No, we're not here anymore. Don't show the /proc/scsi files. */
remove_proc_entry ("scsi/device_info", 0);
remove_proc_entry ("scsi/scsi", 0);
remove_proc_entry ("scsi", 0);
#endif
......
......@@ -475,6 +475,7 @@ extern int scsi_dev_init(void);
extern int scsi_mlqueue_insert(struct scsi_cmnd *, int);
extern int scsi_attach_device(struct scsi_device *);
extern void scsi_detach_device(struct scsi_device *);
extern int scsi_get_device_flags(unsigned char *vendor, unsigned char *model);
/*
* Newer request-based interfaces.
......@@ -513,6 +514,29 @@ extern int print_msg(const unsigned char *);
extern const char *scsi_sense_key_string(unsigned char);
extern const char *scsi_extd_sense_format(unsigned char, unsigned char);
/*
* dev_info: for the black/white list in the old scsi_static_device_list
*/
struct dev_info {
char *vendor;
char *model;
char *revision; /* revision known to be bad, unused */
unsigned flags;
};
extern struct dev_info scsi_static_device_list[] __initdata;
/*
* scsi_dev_info_list: structure to hold black/white listed devices.
*/
struct scsi_dev_info_list {
struct list_head dev_info_list;
char vendor[8];
char model[16];
unsigned flags;
unsigned compatible; /* for use with scsi_static_device_list entries */
};
/*
* The scsi_device struct contains what we know about each given scsi
* device.
......
......@@ -54,18 +54,16 @@
#define BLIST_INQUIRY_36 0x400 /* override additional length field */
#define BLIST_INQUIRY_58 0x800 /* ... for broken inquiry responses */
struct dev_info {
const char *vendor;
const char *model;
const char *revision; /* revision known to be bad, unused */
unsigned flags;
};
/*
* device_list: devices that require settings that differ from the
* default, includes black-listed (broken) devices.
* scsi_static_device_list: deprecated list of devices that require
* settings that differ from the default, includes black-listed (broken)
* devices. The entries here are added to the tail of scsi_dev_info_list
* via scsi_dev_info_list_init.
*
* Do not add to this list, use the command line or proc interface to add
* to the scsi_dev_info_list. This table will eventually go away.
*/
static struct dev_info device_list[] = {
struct dev_info scsi_static_device_list[] __initdata = {
/*
* The following devices are known not to tolerate a lun != 0 scan
* for one reason or another. Some will respond to all luns,
......@@ -183,6 +181,7 @@ static struct dev_info device_list[] = {
{"COMPAQ", "MSA1000", NULL, BLIST_FORCELUN},
{"HP", "C1557A", NULL, BLIST_FORCELUN},
{"IBM", "AuSaV1S2", NULL, BLIST_FORCELUN},
{ NULL, NULL, NULL, 0 },
};
#define ALLOC_FAILURE_MSG KERN_ERR "%s: Allocation failure during" \
......@@ -426,62 +425,6 @@ static void print_inquiry(unsigned char *inq_result)
printk("\n");
}
/**
* get_device_flags - get device specific flags from the device_list
* @vendor: vendor name
* @model: model name
*
* Description:
* Search device_list for an entry matching @vendor and @model, if
* found, return the matching flags value, else return 0.
* Partial matches count as success - good for @model, but maybe not
* @vendor.
**/
static int get_device_flags(unsigned char *vendor, unsigned char *model)
{
int i;
size_t max;
for (i = 0; i < ARRAY_SIZE(device_list); i++) {
/*
* XXX why skip leading spaces? If an odd INQUIRY value,
* that should have been part of the device_list[] entry,
* such as " FOO" rather than "FOO". Since this code is
* already here, and we don't know what device it is
* trying to work with, leave it as-is.
*/
max = 8; /* max length of vendor */
while ((max > 0) && *vendor == ' ') {
max--;
vendor++;
}
/*
* XXX removing the following strlen() would be good,
* using it means that for a an entry not in the list, we
* scan every byte of every vendor listed in
* device_list[], and never match a single one (and still
* have to compare at least the first byte of each
* vendor).
*/
if (memcmp(device_list[i].vendor, vendor,
min(max, strlen(device_list[i].vendor))))
continue;
/*
* Skip spaces again.
*/
max = 16; /* max length of model */
while ((max > 0) && *model == ' ') {
max--;
model++;
}
if (memcmp(device_list[i].model, model,
min(max, strlen(device_list[i].model))))
continue;
return device_list[i].flags;
}
return 0;
}
/**
* scsi_initialize_merge_fn() -ƣinitialize merge function for a host
* @sd: host descriptor
......@@ -1092,12 +1035,12 @@ int scsi_get_default_name(Scsi_Device *sdev)
* Notes:
* If a device returns the same serial number for different LUNs or
* even for different LUNs on different devices, special handling must
* be added to get an id, or a new black list flag must to added and
* used in device_list[] (so we use the default name, or add a way to
* prefix the id/name with SCSI_UID_UNKNOWN - and change the define to
* something meaningful like SCSI_UID_NOT_UNIQUE). Complete user level
* scanning would be nice for such devices, so we do not need device
* specific code in the kernel.
* be added to get an id, or a new black list flag must be added (so
* we use the default name, or add a way to prefix the id/name with
* SCSI_UID_UNKNOWN - and change the define to something meaningful
* like SCSI_UID_NOT_UNIQUE). Complete user level scanning would be
* nice for such devices, so we do not need device specific code in
* the kernel.
**/
static void scsi_load_identifier(Scsi_Device *sdev, Scsi_Request *sreq)
{
......@@ -1176,7 +1119,7 @@ static int scsi_find_scsi_level(unsigned int channel, unsigned int id,
* If the INQUIRY is successful, sreq->sr_result is zero and: the
* INQUIRY data is in @inq_result; the scsi_level and INQUIRY length
* are copied to the Scsi_Device at @sreq->sr_device (sdev);
* any device_list flags value is stored in *@bflags.
* any flags value is stored in *@bflags.
**/
static void scsi_probe_lun(Scsi_Request *sreq, char *inq_result,
int *bflags)
......@@ -1225,7 +1168,7 @@ static void scsi_probe_lun(Scsi_Request *sreq, char *inq_result,
* argument.
*/
BUG_ON(bflags == NULL);
*bflags = get_device_flags(&inq_result[8], &inq_result[16]);
*bflags = scsi_get_device_flags(&inq_result[8], &inq_result[16]);
possible_inq_resp_len = (unsigned char) inq_result[4] + 5;
if (BLIST_INQUIRY_36 & *bflags)
......@@ -1306,7 +1249,7 @@ static void scsi_probe_lun(Scsi_Request *sreq, char *inq_result,
* @sdevnew: store the address of the newly allocated Scsi_Device
* @sreq: scsi request used when getting an identifier
* @inq_result: holds the result of a previous INQUIRY to the LUN
* @bflags: flags value from device_list
* @bflags: black/white list flag
*
* Description:
* Allocate and initialize a Scsi_Device matching sdevscan. Optionally
......@@ -1606,7 +1549,7 @@ static int scsi_probe_and_add_lun(Scsi_Device *sdevscan, Scsi_Device **sdevnew,
/**
* scsi_sequential_lun_scan - sequentially scan a SCSI target
* @sdevscan: scan the host, channel, and id of this Scsi_Device
* @bflags: flags from device_list for LUN 0
* @bflags: black/white list flag for LUN 0
* @lun0_res: result of scanning LUN 0
*
* Description:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment