/* * drivers/s390/cio/blacklist.c * S/390 common I/O routines -- blacklisting of specific devices * * Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH, * IBM Corporation * Author(s): Ingo Adlung (adlung@de.ibm.com) * Cornelia Huck (cornelia.huck@de.ibm.com) * Arnd Bergmann (arndb@de.ibm.com) */ #include #include #include #include #include #include #include #include #include #include #include "blacklist.h" #include "cio.h" #include "cio_debug.h" #include "css.h" /* * "Blacklisting" of certain devices: * Device numbers given in the commandline as cio_ignore=... won't be known * to Linux. * * These can be single devices or ranges of devices */ /* 65536 bits for each set to indicate if a devno is blacklisted or not */ #define __BL_DEV_WORDS ((__MAX_SUBCHANNEL + (8*sizeof(long) - 1)) / \ (8*sizeof(long))) static unsigned long bl_dev[__MAX_SSID + 1][__BL_DEV_WORDS]; typedef enum {add, free} range_action; /* * Function: blacklist_range * (Un-)blacklist the devices from-to */ static inline void blacklist_range (range_action action, unsigned int from, unsigned int to, unsigned int ssid) { if (!to) to = from; if (from > to || to > __MAX_SUBCHANNEL || ssid > __MAX_SSID) { printk (KERN_WARNING "Invalid blacklist range " "0.%x.%04x to 0.%x.%04x, skipping\n", ssid, from, ssid, to); return; } for (; from <= to; from++) { if (action == add) set_bit (from, bl_dev[ssid]); else clear_bit (from, bl_dev[ssid]); } } /* * Function: blacklist_busid * Get devno/busid from given string. * Shamelessly grabbed from dasd_devmap.c. */ static inline int blacklist_busid(char **str, int *id0, int *ssid, int *devno) { int val, old_style; char *sav; sav = *str; /* check for leading '0x' */ old_style = 0; if ((*str)[0] == '0' && (*str)[1] == 'x') { *str += 2; old_style = 1; } if (!isxdigit((*str)[0])) /* We require at least one hex digit */ goto confused; val = simple_strtoul(*str, str, 16); if (old_style || (*str)[0] != '.') { *id0 = *ssid = 0; if (val < 0 || val > 0xffff) goto confused; *devno = val; if ((*str)[0] != ',' && (*str)[0] != '-' && (*str)[0] != '\n' && (*str)[0] != '\0') goto confused; return 0; } /* New style x.y.z busid */ if (val < 0 || val > 0xff) goto confused; *id0 = val; (*str)++; if (!isxdigit((*str)[0])) /* We require at least one hex digit */ goto confused; val = simple_strtoul(*str, str, 16); if (val < 0 || val > 0xff || (*str)++[0] != '.') goto confused; *ssid = val; if (!isxdigit((*str)[0])) /* We require at least one hex digit */ goto confused; val = simple_strtoul(*str, str, 16); if (val < 0 || val > 0xffff) goto confused; *devno = val; if ((*str)[0] != ',' && (*str)[0] != '-' && (*str)[0] != '\n' && (*str)[0] != '\0') goto confused; return 0; confused: strsep(str, ",\n"); printk(KERN_WARNING "Invalid cio_ignore parameter '%s'\n", sav); return 1; } static inline int blacklist_parse_parameters (char *str, range_action action) { unsigned int from, to, from_id0, to_id0, from_ssid, to_ssid; while (*str != 0 && *str != '\n') { range_action ra = action; while(*str == ',') str++; if (*str == '!') { ra = !action; ++str; } /* * Since we have to parse the proc commands and the * kernel arguments we have to check four cases */ if (strncmp(str,"all,",4) == 0 || strcmp(str,"all") == 0 || strncmp(str,"all\n",4) == 0 || strncmp(str,"all ",4) == 0) { int j; str += 3; for (j=0; j <= __MAX_SSID; j++) blacklist_range(ra, 0, __MAX_SUBCHANNEL, j); } else { int rc; rc = blacklist_busid(&str, &from_id0, &from_ssid, &from); if (rc) continue; to = from; to_id0 = from_id0; to_ssid = from_ssid; if (*str == '-') { str++; rc = blacklist_busid(&str, &to_id0, &to_ssid, &to); if (rc) continue; } if (*str == '-') { printk(KERN_WARNING "invalid cio_ignore " "parameter '%s'\n", strsep(&str, ",\n")); continue; } if ((from_id0 != to_id0) || (from_ssid != to_ssid)) { printk(KERN_WARNING "invalid cio_ignore range " "%x.%x.%04x-%x.%x.%04x\n", from_id0, from_ssid, from, to_id0, to_ssid, to); continue; } pr_debug("blacklist_setup: adding range " "from %x.%x.%04x to %x.%x.%04x\n", from_id0, from_ssid, from, to_id0, to_ssid, to); blacklist_range (ra, from, to, to_ssid); } } return 1; } /* Parsing the commandline for blacklist parameters, e.g. to blacklist * bus ids 0.0.1234, 0.0.1235 and 0.0.1236, you could use any of: * - cio_ignore=1234-1236 * - cio_ignore=0x1234-0x1235,1236 * - cio_ignore=0x1234,1235-1236 * - cio_ignore=1236 cio_ignore=1234-0x1236 * - cio_ignore=1234 cio_ignore=1236 cio_ignore=0x1235 * - cio_ignore=0.0.1234-0.0.1236 * - cio_ignore=0.0.1234,0x1235,1236 * - ... */ static int __init blacklist_setup (char *str) { CIO_MSG_EVENT(6, "Reading blacklist parameters\n"); return blacklist_parse_parameters (str, add); } __setup ("cio_ignore=", blacklist_setup); /* Checking if devices are blacklisted */ /* * Function: is_blacklisted * Returns 1 if the given devicenumber can be found in the blacklist, * otherwise 0. * Used by validate_subchannel() */ int is_blacklisted (int ssid, int devno) { return test_bit (devno, bl_dev[ssid]); } #ifdef CONFIG_PROC_FS static int __s390_redo_validation(struct subchannel_id schid, void *data) { int ret; struct subchannel *sch; sch = get_subchannel_by_schid(schid); if (sch) { /* Already known. */ put_device(&sch->dev); return 0; } ret = css_probe_device(schid); if (ret == -ENXIO) return ret; /* We're through. */ if (ret == -ENOMEM) /* Stop validation for now. Bad, but no need for a panic. */ return ret; return 0; } /* * Function: s390_redo_validation * Look for no longer blacklisted devices * FIXME: there must be a better way to do this */ static inline void s390_redo_validation (void) { CIO_TRACE_EVENT (0, "redoval"); for_each_subchannel(__s390_redo_validation, NULL); } /* * Function: blacklist_parse_proc_parameters * parse the stuff which is piped to /proc/cio_ignore */ static inline void blacklist_parse_proc_parameters (char *buf) { if (strncmp (buf, "free ", 5) == 0) { blacklist_parse_parameters (buf + 5, free); } else if (strncmp (buf, "add ", 4) == 0) { /* * We don't need to check for known devices since * css_probe_device will handle this correctly. */ blacklist_parse_parameters (buf + 4, add); } else { printk (KERN_WARNING "cio_ignore: Parse error; \n" KERN_WARNING "try using 'free all|," ",...'\n" KERN_WARNING "or 'add ," ",...'\n"); return; } s390_redo_validation (); } /* Iterator struct for all devices. */ struct ccwdev_iter { int devno; int ssid; int in_range; }; static void * cio_ignore_proc_seq_start(struct seq_file *s, loff_t *offset) { struct ccwdev_iter *iter; if (*offset >= (__MAX_SUBCHANNEL + 1) * (__MAX_SSID + 1)) return NULL; iter = kzalloc(sizeof(struct ccwdev_iter), GFP_KERNEL); if (!iter) return ERR_PTR(-ENOMEM); iter->ssid = *offset / (__MAX_SUBCHANNEL + 1); iter->devno = *offset % (__MAX_SUBCHANNEL + 1); return iter; } static void cio_ignore_proc_seq_stop(struct seq_file *s, void *it) { if (!IS_ERR(it)) kfree(it); } static void * cio_ignore_proc_seq_next(struct seq_file *s, void *it, loff_t *offset) { struct ccwdev_iter *iter; if (*offset >= (__MAX_SUBCHANNEL + 1) * (__MAX_SSID + 1)) return NULL; iter = it; if (iter->devno == __MAX_SUBCHANNEL) { iter->devno = 0; iter->ssid++; if (iter->ssid > __MAX_SSID) return NULL; } else iter->devno++; (*offset)++; return iter; } static int cio_ignore_proc_seq_show(struct seq_file *s, void *it) { struct ccwdev_iter *iter; iter = it; if (!is_blacklisted(iter->ssid, iter->devno)) /* Not blacklisted, nothing to output. */ return 0; if (!iter->in_range) { /* First device in range. */ if ((iter->devno == __MAX_SUBCHANNEL) || !is_blacklisted(iter->ssid, iter->devno + 1)) /* Singular device. */ return seq_printf(s, "0.%x.%04x\n", iter->ssid, iter->devno); iter->in_range = 1; return seq_printf(s, "0.%x.%04x-", iter->ssid, iter->devno); } if ((iter->devno == __MAX_SUBCHANNEL) || !is_blacklisted(iter->ssid, iter->devno + 1)) { /* Last device in range. */ iter->in_range = 0; return seq_printf(s, "0.%x.%04x\n", iter->ssid, iter->devno); } return 0; } static ssize_t cio_ignore_write(struct file *file, const char __user *user_buf, size_t user_len, loff_t *offset) { char *buf; if (*offset) return -EINVAL; if (user_len > 65536) user_len = 65536; buf = vmalloc (user_len + 1); /* maybe better use the stack? */ if (buf == NULL) return -ENOMEM; if (strncpy_from_user (buf, user_buf, user_len) < 0) { vfree (buf); return -EFAULT; } buf[user_len] = '\0'; blacklist_parse_proc_parameters (buf); vfree (buf); return user_len; } static struct seq_operations cio_ignore_proc_seq_ops = { .start = cio_ignore_proc_seq_start, .stop = cio_ignore_proc_seq_stop, .next = cio_ignore_proc_seq_next, .show = cio_ignore_proc_seq_show, }; static int cio_ignore_proc_open(struct inode *inode, struct file *file) { return seq_open(file, &cio_ignore_proc_seq_ops); } static struct file_operations cio_ignore_proc_fops = { .open = cio_ignore_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, .write = cio_ignore_write, }; static int cio_ignore_proc_init (void) { struct proc_dir_entry *entry; entry = create_proc_entry ("cio_ignore", S_IFREG | S_IRUGO | S_IWUSR, &proc_root); if (!entry) return -ENOENT; entry->proc_fops = &cio_ignore_proc_fops; return 0; } __initcall (cio_ignore_proc_init); #endif /* CONFIG_PROC_FS */