/* * zdev - Modify and display the persistent configuration of devices * * Copyright IBM Corp. 2016, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include "lib/util_base.h" #include "lib/util_path.h" #include "attrib.h" #include "ccw.h" #include "ccwgroup.h" #include "device.h" #include "devnode.h" #include "devtype.h" #include "misc.h" #include "module.h" #include "namespace.h" #include "path.h" #include "setting.h" #include "udev.h" #include "udev_ccw.h" #define DEVNAME "CCW device" #define CSSID_MAX 255 #define SSID_MAX 15 #define DEVNO_MAX 65535 #define CCW_HASH_BUCKETS 256 /* * Common CCW device attributes and related functions. */ struct attrib ccw_attr_online = { .name = "online", .title = "Activate a device", .desc = "Control the activation of a device:\n" " 0: Device is disabled and cannot be used\n" " 1: Device is enabled\n", .mandatory = 1, .defval = "0", .accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)), }; struct attrib ccw_attr_online_force = { .name = "online", .title = "Activate a device", .desc = "Control the activation of a device:\n" " 0: Device is disabled and cannot be used\n" " 1: Device is enabled\n" " force: Release an existing reservation for this device\n" " before trying to enable it\n", .mandatory = 1, .defval = "0", .accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1), ACCEPT_STR("force")), }; struct attrib ccw_attr_cmb_enable = { .name = "cmb_enable", .title = "Enable the Channel measurement facility", .desc = "Control the channel measurement facility setting for a device:\n" " 0: Data collection is disabled\n" " 1: Data collection is enabled (rewrite 1 to reset data)\n", .rewrite = 1, .defval = "0", .order_cmp = ccw_offline_only_order_cmp, .accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1)), }; static bool is_online(const char *value) { if (!value) return false; if (strcmp(value, "force") == 0 || atoi(value) == 1) return true; return false; } static bool is_offline(const char *value) { if (!value) return false; if (atoi(value) == 0) return true; return false; } /* Function to be used as order_cmp callback for attributes that can only be * set while a CCW device is online. @a is the attribute in question, while * @b can be any other setting, including the online attribute. */ int ccw_online_only_order_cmp(struct setting *a, struct setting *b) { if (b->attrib != &ccw_attr_online && b->attrib != &ccw_attr_online_force) return 0; /* This setting should be applied before online=0 (assuming it * started online) or after online=1. */ if (is_online(b->value)) return 1; return -1; } /* Function to be used as check callback for attributes that can only be * set while a CCW device is online. @a is the attribute in question, while * @b can be any other setting, including the online attribute. Return * %false in case there is a conflict, %true otherwise. */ bool ccw_online_only_check(struct setting *a, struct setting *b, config_t config) { if (b->attrib != &ccw_attr_online && b->attrib != &ccw_attr_online_force) return true; /* No conflict if: * - online=1: setting can be applied * - online is changed to 1: setting can be applied after change * - online is changed from 1 to 0 in the active configuration: * setting can be applied before change */ if (is_online(b->value) || (SCOPE_ACTIVE(config) && is_online(b->actual_value))) return true; return false; } /* Function to be used as order_cmp callback for attributes that can only be * set while a CCW device is offline. @a is the attribute in question, while * @b can be any other setting, including the online attribute. */ int ccw_offline_only_order_cmp(struct setting *a, struct setting *b) { if (b->attrib != &ccw_attr_online && b->attrib != &ccw_attr_online_force) return 0; /* This setting should be applied before online=1 (assuming it * started offline) or after online=0. */ if (is_online(b->value)) return -1; return 1; } /* Function to be used as check callback for attributes that can only be * set while a CCW device is offline. @a is the attribute in question, while * @b can be any other setting, including the online attribute. Return * %false in case there is a conflict, %true otherwise. */ bool ccw_offline_only_check(struct setting *a, struct setting *b, config_t config) { if (b->attrib != &ccw_attr_online && b->attrib != &ccw_attr_online_force) return true; if (!SCOPE_ACTIVE(config)) { /* CCW devices start in offline mode so no conflict. */ return true; } /* No conflict if: * - online=0: setting can be applied * - online is changed to 0: setting can be applied after change * - online is changed from 0 to 1: setting can be applied before * change */ if (is_offline(b->value) || is_offline(b->actual_value)) return true; return false; } /* * CCW device methods. */ /** * ccw_parse_devid - Parse a string into a ccw_devid * @devid_ptr: Target ccw_devid or NULL * @id: String to parse * @err: Specify what to do with errors (ignore, print, delayed_print) * * Return %EXIT_OK on success, a corresponding error exit code otherwise. */ exit_code_t ccw_parse_devid(struct ccw_devid *devid_ptr, const char *id, err_t err) { unsigned int cssid, ssid, devno; char d; char *reason = NULL; exit_code_t rc = EXIT_INVALID_ID; if (sscanf(id, "%8x %c", &devno, &d) == 1) { if (strcasecmp(id, "0x") == 0) { reason = "Incomplete hexadecimal number"; goto out; } cssid = 0; ssid = 0; } else if (sscanf(id, "%8x.%8x.%8x %c", &cssid, &ssid, &devno, &d) != 3) { reason = "Invalid format"; goto out; } if (cssid > CSSID_MAX) { reason = "CSSID out of bounds"; goto out; } if (ssid > SSID_MAX) { reason = "SSID out of bounds"; goto out; } if (devno > DEVNO_MAX) { reason = "DEVNO out of bounds"; goto out; } rc = EXIT_OK; if (devid_ptr) { devid_ptr->cssid = cssid; devid_ptr->ssid = ssid; devid_ptr->devno = devno; } out: if (reason) { err_t_print(err, "Error in %s ID format: %s: %s\n", DEVNAME, reason, id); } return rc; } bool ccw_parse_devid_simple(struct ccw_devid *devid, const char *id) { if (ccw_parse_devid(devid, id, err_ignore) == EXIT_OK) return true; return false; } static exit_code_t ccw_is_id_valid(const char *id, err_t err) { return ccw_parse_devid(NULL, id, err); } bool ccw_is_id_similar(const char *id) { char *copy, *start, *end; bool result = false; copy = misc_strdup(id); start = copy; if (strchr(start, ':')) goto out; /* xx.xx.xxxx or xxxx or 0xxxxx */ end = strchr(start, '.'); if (end) { /* Check for a list of 2 dot-separated hexadecimal numbers. */ *end = 0; if (!valid_hex(start) || strlen(start) > 8) goto out; start = end + 1; end = strchr(start, '.'); if (!end) goto out; *end = 0; if (!valid_hex(start) || strlen(start) > 8) goto out; start = end + 1; } /* Check if this is a valid hexadecimal number. */ if (valid_hex(start) && strlen(start) <= 8) result = true; out: free(copy); return result; } static int ccw_cmp_parsed_ids(const void *a, const void *b) { return memcmp(a, b, sizeof(struct ccw_devid)); } static int ccw_cmp_ids(const char *a_str, const char *b_str) { struct ccw_devid a, b; if (!ccw_parse_devid_simple(&a, a_str) || !ccw_parse_devid_simple(&b, b_str)) return -1; return ccw_cmp_parsed_ids(&a, &b); } static int ccw_qsort_cmp(const void *a_ptr, const void *b_ptr) { const char *a = *((const char **) a_ptr); const char *b = *((const char **) b_ptr); return ccw_cmp_ids(a, b); } static int ccw_hash_by_parsed_id(const void *devid_ptr) { const struct ccw_devid *devid = devid_ptr; return devid->devno & 0xff; } /* Return distance between CCW device IDs %a and %b in number of CCW device * IDs or INT_MAX if both IDs are not in the same CSS/SS. */ int ccw_devid_distance(struct ccw_devid *a, struct ccw_devid *b) { if (a->cssid != b->cssid) return INT_MAX; if (a->ssid != b->ssid) return INT_MAX; return (int) b->devno - (int) a->devno; } char *ccw_devid_to_str(struct ccw_devid *devid) { return misc_asprintf("%x.%x.%04x", devid->cssid, devid->ssid, devid->devno); } char *ccw_normalize_id(const char *id) { struct ccw_devid devid; if (!ccw_parse_devid_simple(&devid, id)) return NULL; return ccw_devid_to_str(&devid); } static exit_code_t ccw_parse_devid_range(struct ccw_devid *from_ptr, struct ccw_devid *to_ptr, const char *range, err_t err) { char *from_str, *to_str; struct ccw_devid from, to; exit_code_t rc = EXIT_INVALID_ID; const char *reason = NULL; /* Split range. */ from_str = misc_strdup(range); to_str = strchr(from_str, '-'); if (!to_str) { reason = "Missing hyphen"; goto out; } *to_str = 0; to_str++; /* Parse range start end end ID. */ rc = ccw_parse_devid(&from, from_str, err); if (rc) goto out; rc = ccw_parse_devid(&to, to_str, err); if (rc) goto out; rc = EXIT_OK; if (from_ptr) *from_ptr = from; if (to_ptr) *to_ptr = to; out: free(from_str); if (reason) { err_t_print(err, "Error in %s ID range format: %s: %s\n", DEVNAME, reason, range); } return rc; } static bool ccw_parse_devid_range_simple(struct ccw_devid *from, struct ccw_devid *to, const char *range) { if (ccw_parse_devid_range(from, to, range, err_ignore) == EXIT_OK) return true; return false; } static exit_code_t ccw_is_id_range_valid(const char *range, err_t err) { return ccw_parse_devid_range(NULL, NULL, range, err); } static unsigned long ccw_num_ids_in_range(const char *range) { struct ccw_devid f, t; if (!ccw_parse_devid_range_simple(&f, &t, range)) return 0; if (f.cssid != t.cssid || f.ssid != t.ssid) return 0; if (f.devno > t.devno) return 0; return t.devno - f.devno + 1; } static struct ccw_devid *copy_devid(struct ccw_devid *devid) { struct ccw_devid *result; result = misc_malloc(sizeof(struct ccw_devid)); *result = *devid; return result; } /* Initialize the range iterator object IT with the specified range and * set it->id to the first ID in the range or NULL if the range is not valid. * All used fields in IT must be objects allocated using alloc(). */ static void ccw_range_start(struct ns_range_iterator *it, const char *range) { struct ccw_devid from, to; if (!ccw_parse_devid_range_simple(&from, &to, range)) { memset(it, 0, sizeof(struct ns_range_iterator)); return; } it->devid = copy_devid(&from); it->devid_last = copy_devid(&to); it->id = ccw_devid_to_str(it->devid); } /* Set it->id to the next ID in the range or NULL if the end of the range * was reached. */ static void ccw_range_next(struct ns_range_iterator *it) { struct ccw_devid *curr, *last; if (!it->id) return; free(it->id); curr = it->devid; last = it->devid_last; if (curr->devno < last->devno) { curr->devno++; it->id = ccw_devid_to_str(curr); } else it->id = NULL; } static void *ccw_parse_id(const char *id, err_t err) { struct ccw_devid *devid; devid = misc_malloc(sizeof(struct ccw_devid)); if (ccw_parse_devid(devid, id, err) != EXIT_OK) { free(devid); return NULL; } return devid; } int ccw_cmp_devids(struct ccw_devid *a, struct ccw_devid *b) { return ccw_cmp_parsed_ids(a, b); } /* * cio_ignore handling. */ static char *proc_cio_ignore; static struct util_list *ignore_once_list; static struct util_list *devinfos; /* Release memory used by CCW module. */ void ccw_exit(void) { free(proc_cio_ignore); strlist_free(ignore_once_list); ptrlist_free(devinfos, 1); } /* Before accessing a CCW device, ensure that the common I/O layer has finished * processing all events. This is done only once unless FORCE is set to a * non-zero value. */ void cio_settle(int force) { static int done; char *path; if (done && !force) return; path = path_get_proc("cio_settle"); misc_write_text_file(path, "\n", err_ignore); free(path); done = 1; } /* Determine the name of the CCW driver associated with the specified device. */ char *ccw_get_driver(struct ccw_devid *devid) { char *id, *path, *driver_path, *link, *drv = NULL; id = ccw_devid_to_str(devid); path = path_get_ccw_device(NULL, id); driver_path = misc_asprintf("%s/driver", path); link = misc_readlink(driver_path); if (link) drv = misc_strdup(basename(link)); free(link); free(driver_path); free(path); free(id); return drv; } /* Return the name of the kernel module associated with the specified device. */ static char *ccw_get_module(struct ccw_devid *devid) { char *id, *path, *driver_path, *link, *module = NULL; id = ccw_devid_to_str(devid); path = path_get_ccw_device(NULL, id); driver_path = misc_asprintf("%s/driver/module", path); link = misc_readlink(driver_path); if (link) module = misc_strdup(basename(link)); free(link); free(driver_path); free(path); free(id); return module; } /* Unbind a CCW device from its driver. */ exit_code_t ccw_unbind_device(struct ccw_devid *devid) { char *id, *path, *unbind_path; exit_code_t rc; id = ccw_devid_to_str(devid); path = path_get_ccw_device(NULL, id); unbind_path = misc_asprintf("%s/driver/unbind", path); rc = misc_write_text_file(unbind_path, id, err_delayed_print); if (rc) delayed_err("Could not unbind CCW device %s from driver\n", id); free(unbind_path); free(path); free(id); return rc; } /* Bind a CCW device to a driver. */ exit_code_t ccw_bind_device(struct ccw_devid *devid, const char *drv) { char *id, *path, *bind_path; exit_code_t rc; id = ccw_devid_to_str(devid); path = path_get_sys_bus_drv(CCW_BUS_NAME, drv); bind_path = misc_asprintf("%s/bind", path); rc = misc_write_text_file(bind_path, id, err_delayed_print); if (rc) { delayed_err("Could not bind CCW device %s to driver %s\n", id, drv); } free(bind_path); free(path); free(id); return rc; } /* Check if the specified device ID is within the range specified by FROM * and TO. */ bool ccw_devid_in_range(struct ccw_devid *id, struct ccw_devid *from, struct ccw_devid *to) { if (ccw_cmp_parsed_ids(id, from) < 0 || ccw_cmp_parsed_ids(id, to) > 0) return false; return true; } /* Check if @id is in @range. */ static bool ccw_is_id_in_range(const char *id, const char *range) { struct ccw_devid devid, from, to; if (!ccw_parse_devid_range_simple(&from, &to, range)) return false; if (!ccw_parse_devid_simple(&devid, id)) return false; return ccw_devid_in_range(&devid, &from, &to); } /* Return a newly allocated copy of @devid. */ struct ccw_devid *ccw_copy_devid(struct ccw_devid *devid) { struct ccw_devid *result; result = misc_malloc(sizeof(struct ccw_devid)); *result = *devid; return result; } static char *read_cio_ignore(void) { static int warned; char *path; if (proc_cio_ignore) return proc_cio_ignore; path = path_get_proc("cio_ignore"); proc_cio_ignore = misc_read_text_file(path, 0, err_ignore); if (!proc_cio_ignore) { if (!warned) { warn("Failed to read %s: Could not access CIO " "blacklist\n", path); } warned = 1; } free(path); return proc_cio_ignore; } /* Check if there is a CCW device blacklist active. */ bool ccw_is_blacklist_active(void) { read_cio_ignore(); if (!proc_cio_ignore || !*proc_cio_ignore) return false; return true; } static bool is_ignored(const char *str, int is_range) { char *copy, *next, *curr; bool rc = false; struct ccw_devid id, id2, from, to; if (is_range) { if (!ccw_parse_devid_range_simple(&id, &id2, str)) return false; } else { if (!ccw_parse_devid_simple(&id, str)) return false; } /* Read /proc/cio_ignore. */ if (!read_cio_ignore()) return false; /* Iterate over each line. */ copy = misc_strdup(proc_cio_ignore); next = copy; while ((curr = strsep(&next, "\n"))) { if (*curr == 0) break; if (ccw_parse_devid_simple(&from, curr)) { /* Single ID: xx.y.zzzz */ if (is_range) { /* Is FROM within ID-ID2? */ if (!ccw_devid_in_range(&from, &id, &id2)) continue; } else { /* Is FROM the same as ID? */ if (ccw_cmp_parsed_ids(&id, &from) != 0) continue; } goto found; } if (!ccw_parse_devid_range_simple(&from, &to, curr)) continue; /* ID range: xx.y.zzzz-xx.y.zzzz */ if (is_range) { /* Does FROM-TO intersect with ID-ID2 ? */ if (ccw_cmp_parsed_ids(&to, &id) < 0 || ccw_cmp_parsed_ids(&from, &id2) > 0) continue; } else { /* Is ID inside FROM-TO? */ if (!ccw_devid_in_range(&id, &from, &to)) continue; } found: rc = true; break; } free(copy); return rc; } /* Determine if the specified CCW device ID is on the cio_ignore blacklist. */ bool ccw_is_id_blacklisted(const char *id) { return is_ignored(id, 0); } /* Determine if a device ID in the specified CCW device ID range is on the * cio_ignore blacklist. */ bool ccw_is_id_range_blacklisted(const char *range) { return is_ignored(range, 1); } /* Remove a CCW device ID from the CIO blacklist. Ensure that this is done * only once. */ void ccw_unblacklist_id(const char *id) { char *normid, *path, *line; /* Ensure that this is only attempted once. */ normid = ccw_normalize_id(id); if (!normid) return; if (!ignore_once_list) ignore_once_list = strlist_new(); if (strlist_find(ignore_once_list, normid)) { free(normid); return; } verb("Removing CCW device %s from the CIO blacklist\n", normid); strlist_add(ignore_once_list, normid); free(normid); path = path_get_proc("cio_ignore"); line = misc_asprintf("free %s", id); if (misc_write_text_file(path, line, err_ignore)) warn("Could not remove %s from the CIO blacklist\n", id); cio_settle(1); free(line); free(path); free(proc_cio_ignore); /* Ensure that cio_ignore file is re-read. */ proc_cio_ignore = NULL; } /* Remove a CCW device ID range from the CIO blacklist. */ void ccw_unblacklist_id_range(const char *range) { char *path, *line; verb("Removing CCW device ID range %s from the CIO blacklist\n", range); path = path_get_proc("cio_ignore"); line = misc_asprintf("free %s", range); if (misc_write_text_file(path, line, err_ignore)) warn("Could not remove %s from the CIO blacklist\n", range); cio_settle(1); /* Need to wait for udev or persistent changes might accidentally * become activated due to delayed register events. */ udev_settle(); free(line); free(path); free(proc_cio_ignore); /* Ensure that cio_ignore file is re-read. */ proc_cio_ignore = NULL; } static char ***id_bitmap_new(void) { return misc_malloc(sizeof(char **) * CSSID_MAX); } static void id_bitmap_free(char ***id_bitmap) { unsigned int cssid, ssid; for (cssid = 0; cssid < CSSID_MAX; cssid++) { if (!id_bitmap[cssid]) continue; for (ssid = 0; ssid < SSID_MAX; ssid++) free(id_bitmap[cssid][ssid]); free(id_bitmap[cssid]); } free(id_bitmap); } #define BITS_PER_CHAR 8 static void id_bitmap_set(char ***id_bitmap, unsigned int cssid, unsigned int ssid, unsigned int devno) { unsigned int offset, bit; if (!id_bitmap[cssid]) id_bitmap[cssid] = misc_malloc(sizeof(char *) * (SSID_MAX + 1)); if (!id_bitmap[cssid][ssid]) { id_bitmap[cssid][ssid] = misc_malloc(sizeof(char) * (DEVNO_MAX + 1) / BITS_PER_CHAR); } offset = devno / BITS_PER_CHAR; bit = devno % BITS_PER_CHAR; id_bitmap[cssid][ssid][offset] |= 1 << bit; } static bool id_bitmap_get(char ***id_bitmap, unsigned int cssid, unsigned int ssid, unsigned int devno) { unsigned int offset, bit; if (!id_bitmap[cssid]) return false; if (!id_bitmap[cssid][ssid]) return false; offset = devno / BITS_PER_CHAR; bit = devno % BITS_PER_CHAR; if (id_bitmap[cssid][ssid][offset] & 1 << bit) return true; return false; } static exit_code_t collect_cb(struct subtype *st, const char *id, config_t config, void *data) { char ***id_bitmap = data; struct ccw_devid devid; if (ccw_parse_devid_simple(&devid, id)) id_bitmap_set(id_bitmap, devid.cssid, devid.ssid, devid.devno); return EXIT_OK; } static exit_code_t collect_group_cb(struct subtype *st, const char *id, config_t config, void *data) { char ***id_bitmap = data; struct ccwgroup_devid devid; unsigned int i; if (ccwgroup_parse_devid_simple(&devid, id)) { for (i = 0; i < devid.num; i++) { id_bitmap_set(id_bitmap, devid.devid[i].cssid, devid.devid[i].ssid, devid.devid[i].devno); } } return EXIT_OK; } /* Set bits for all persistently configured CCW devices. */ static char ***id_bitmap_collect(bool autoconf) { char ***id_bitmap; int i, j; struct devtype *dt; struct subtype *st; config_t config = autoconf ? config_autoconf : config_persistent; id_bitmap = id_bitmap_new(); /* Search all subtypes. */ for (i = 0; (dt = devtypes[i]); i++) { for (j = 0; (st = dt->subtypes[j]); j++) { /* Collect CCW device IDs. */ if (st->namespace == &ccw_namespace) { subtype_for_each_id(st, config, collect_cb, id_bitmap); } /* Collect CCWGROUP device IDs. Since there may be * multiple namespaces, we need to make use of this * hack. */ if (ccwgroup_compatible_namespace(st->namespace)) { subtype_for_each_id(st, config, collect_group_cb, id_bitmap); } } } return id_bitmap; } struct ccw_devid_range { struct ccw_devid from; struct ccw_devid to; }; static void range_add(struct util_list *list, struct ccw_devid *from, struct ccw_devid *to) { char *f_str, *fs_str, *t_str = NULL, *ts_str; f_str = ccw_devid_to_str(from); if (from->cssid == 0 && from->ssid == 0) fs_str = f_str + 4; else fs_str = f_str; if (to) { t_str = ccw_devid_to_str(to); if (to->cssid == 0 && to->ssid == 0) ts_str = t_str + 4; else ts_str = t_str; strlist_add(list, "%s-%s", fs_str, ts_str); } else strlist_add(list, "%s", fs_str); free(t_str); free(f_str); } static struct util_list *cio_ignore_get_ranges(bool autoconf) { char ***id_bitmap; unsigned int cssid, ssid, devno; struct util_list *ranges; struct ccw_devid a, b; struct ccw_devid *first, *last; ranges = strlist_new(); id_bitmap = id_bitmap_collect(autoconf); for (cssid = 0; cssid < CSSID_MAX; cssid++) { if (!id_bitmap[cssid]) continue; for (ssid = 0; ssid < SSID_MAX; ssid++) { if (!id_bitmap[cssid][ssid]) continue; first = NULL; last = NULL; for (devno = 0; devno < DEVNO_MAX; devno++) { if (!id_bitmap_get(id_bitmap, cssid, ssid, devno)) { if (first) { range_add(ranges, first, last); first = NULL; last = NULL; } continue; } if (first) { last = &b; last->cssid = cssid; last->ssid = ssid; last->devno = devno; } else { first = &a; first->cssid = cssid; first->ssid = ssid; first->devno = devno; } } if (first) range_add(ranges, first, last); } } id_bitmap_free(id_bitmap); return ranges; } /* Persistently configure cio_ignore. */ static exit_code_t _ccw_blacklist_persist(bool autoconf) { struct util_list *ranges; char *id_list; exit_code_t rc; /* Get string to write to /proc/cio_ignore. */ ranges = cio_ignore_get_ranges(autoconf); id_list = strlist_flatten(ranges, ","); strlist_free(ranges); /* Write udev rule to automatically write cio_ignore. */ rc = udev_ccw_write_cio_ignore(id_list, autoconf); free(id_list); return rc; } exit_code_t ccw_blacklist_persist(void) { exit_code_t rc1, rc2; rc1 = _ccw_blacklist_persist(false); rc2 = _ccw_blacklist_persist(true); return rc1 ? rc1 : rc2; } /* * CCW device ID namespace. */ struct namespace ccw_namespace = { .devname = DEVNAME, /* IDs. */ .is_id_valid = ccw_is_id_valid, .is_id_similar = ccw_is_id_similar, .cmp_ids = ccw_cmp_ids, .normalize_id = ccw_normalize_id, .parse_id = ccw_parse_id, .cmp_parsed_ids = ccw_cmp_parsed_ids, .qsort_cmp = ccw_qsort_cmp, /* ID Hash. */ .hash_buckets = CCW_HASH_BUCKETS, .hash_parsed_id = ccw_hash_by_parsed_id, /* Ranges. */ .is_id_range_valid = ccw_is_id_range_valid, .num_ids_in_range = ccw_num_ids_in_range, .is_id_in_range = ccw_is_id_in_range, .range_start = ccw_range_start, .range_next = ccw_range_next, /* Blacklist handling. */ .is_blacklist_active = ccw_is_blacklist_active, .is_id_blacklisted = ccw_is_id_blacklisted, .is_id_range_blacklisted = ccw_is_id_range_blacklisted, .unblacklist_id = ccw_unblacklist_id, .unblacklist_id_range = ccw_unblacklist_id_range, .blacklist_persist = ccw_blacklist_persist, }; /* * CCW device info handling. */ /* Used for debugging. */ void ccw_devinfo_print(struct ccw_devinfo *d, int level) { char *id; int i; printf("%*sdevinfo at %p\n", level, "", (void *) d); if (!d) return; level += 2; id = ccw_devid_to_str(&d->devid); printf("%*sdevid=%s\n", level, "", id); printf("%*scutype=%04x/%02x\n", level, "", d->cutype, d->cumodel); printf("%*sdevtype=%04x/%02x\n", level, "", d->devtype, d->devmodel); printf("%*schpids=", level, ""); for (i = 0; i < CCW_CHPID_NUM; i++) { printf("%" PRIx8 ".%02" PRIx8 " ", d->chpids[i].cssid, d->chpids[i].id); } printf("\n"); printf("%*spim=%02x\n", level, "", d->pim); printf("%*sexists=%d\n", level, "", d->exists); printf("%*sgrouped=%d\n", level, "", d->grouped); } static struct ccw_devinfo *ccw_devinfo_find(struct ccw_devid *devid) { struct ccw_devinfo *info; struct ptrlist_node *p; if (!devinfos) return NULL; util_list_iterate(devinfos, p) { info = p->ptr; if (ccw_cmp_devids(devid, &info->devid) == 0) return info; } return NULL; } static bool read_pim(struct ccw_devinfo *info, const char *path) { char *file_path, *text; bool result = false; uint8_t pim; file_path = misc_asprintf("%s/../pimpampom", path); text = misc_read_text_file(file_path, 1, err_ignore); if (!text) goto out; if (sscanf(text, "%" SCNx8, &pim) != 1) goto out; info->pim = pim; result = true; out: free(text); free(file_path); return result; } static bool read_chpids(struct ccw_devinfo *info, unsigned int cssid, unsigned int pim, const char *path) { char *file_path, *text; bool result = false; struct ccw_chpid ids[CCW_CHPID_NUM]; int i; file_path = misc_asprintf("%s/../chpids", path); text = misc_read_text_file(file_path, 1, err_ignore); if (!text) goto out; if (sscanf(text, "%" SCNx8 " %" SCNx8 " %" SCNx8 " %" SCNx8 "%" SCNx8 " %" SCNx8 " %" SCNx8 " %" SCNx8, &ids[0].id, &ids[1].id, &ids[2].id, &ids[3].id, &ids[4].id, &ids[5].id, &ids[6].id, &ids[7].id) != 8) goto out; /* Apply CSS-ID to CHPIDs. */ for (i = 0; i < CCW_CHPID_NUM; i++) { if (pim & CCW_CHPID_MASK(i)) ids[i].cssid = cssid; else ids[i].cssid = 0; } memcpy(&info->chpids, ids, sizeof(ids)); result = true; out: free(text); free(file_path); return result; } static void read_grouped(struct ccw_devinfo *info, const char *path) { if (util_path_exists("%s/group_device", path)) info->grouped = 1; else info->grouped = 0; } static void read_cutype(struct ccw_devinfo *info, const char *path) { char *file_path, *text; unsigned int cutype = 0, cumodel = 0; file_path = misc_asprintf("%s/cutype", path); text = misc_read_text_file(file_path, 1, err_ignore); if (!text) goto out; if (sscanf(text, "%04x/%02x", &cutype, &cumodel) != 2) goto out; out: info->cutype = cutype; info->cumodel = cumodel; free(text); free(file_path); } static void read_devtype(struct ccw_devinfo *info, const char *path) { char *file_path, *text; unsigned int devtype = 0, devmodel = 0; file_path = misc_asprintf("%s/devtype", path); text = misc_read_text_file(file_path, 1, err_ignore); if (!text) goto out; if (sscanf(text, "%04x/%02x", &devtype, &devmodel) != 2) goto out; out: info->devtype = devtype; info->devmodel = devmodel; free(text); free(file_path); } /* Read device information from sysfs. */ static struct ccw_devinfo *ccw_devinfo_read(struct ccw_devid *devid) { struct ccw_devinfo *info; char *id, *path; info = misc_malloc(sizeof(struct ccw_devinfo)); info->devid = *devid; id = ccw_devid_to_str(devid); path = path_get_ccw_device(NULL, id); if (!util_path_is_dir(path)) goto out; info->exists = 1; if (read_pim(info, path)) read_chpids(info, devid->cssid, info->pim, path); read_grouped(info, path); read_cutype(info, path); read_devtype(info, path); out: free(path); free(id); return info; } static struct ccw_devinfo *ccw_devinfo_copy(struct ccw_devinfo *info) { struct ccw_devinfo *result; result = misc_malloc(sizeof(struct ccw_devinfo)); memcpy(result, info, sizeof(struct ccw_devinfo)); return result; } /* Return a newly allocated copy of a struct ccw_devinfo describing the * CCW device with the specified CCW device ID. */ struct ccw_devinfo *ccw_devinfo_get(struct ccw_devid *devid, int reload) { struct ccw_devinfo *info = NULL; if (reload) return ccw_devinfo_read(devid); info = ccw_devinfo_find(devid); if (!info) { info = ccw_devinfo_read(devid); if (!devinfos) devinfos = ptrlist_new(); ptrlist_add(devinfos, info); } return ccw_devinfo_copy(info); } /* Compare two ccw_devinfos by CHPID. */ int ccw_devinfo_chpids_cmp(struct ccw_devinfo *a, struct ccw_devinfo *b) { if (a->pim > b->pim) return 1; if (a->pim < b->pim) return -1; return memcmp(&a->chpids, &b->chpids, sizeof(a->chpids)); } /* Compare two ccw_devinfos by CUTYPE. */ int ccw_devinfo_cutype_cmp(struct ccw_devinfo *a, struct ccw_devinfo *b) { if (a->cutype > b->cutype) return 1; if (a->cutype < b->cutype) return -1; if (a->cumodel > b->cumodel) return 1; if (a->cumodel < b->cumodel) return -1; return 0; } /* Compare two ccw_devinfos by DEVYPE. */ int ccw_devinfo_devtype_cmp(struct ccw_devinfo *a, struct ccw_devinfo *b) { if (a->devtype > b->devtype) return 1; if (a->devtype < b->devtype) return -1; if (a->devmodel > b->devmodel) return 1; if (a->devmodel < b->devmodel) return -1; return 0; } /* * CCW device base subtype. */ static char *ccw_get_dev_path(const char *drv, const char *id) { char *normid, *path; normid = ccw_normalize_id(id); if (!normid) return NULL; path = path_get_ccw_device(drv, normid); free(normid); return path; } /* Check if a CCW device with the given ID exists. If DRV is non-null, * only check for devices attached to the specified driver. */ bool ccw_exists(const char *drv, const char *mod, const char *id) { char *path; bool rc; cio_settle(0); path = path_get_ccw_devices(drv); if (mod) module_try_load_once(mod, path); free(path); path = ccw_get_dev_path(drv, id); if (!path) return false; rc = util_path_is_dir(path); free(path); return rc; } /* Check if a CCW device with the specified @id exists in the active * configuration. */ static bool ccw_st_exists_active(struct subtype *st, const char *id) { struct ccw_subtype_data *data = st->data; return ccw_exists(data->ccwdrv, data->mod, id); } /* Check if a configuration exists for a CCW device with the specified @id. */ static bool ccw_st_exists_persistent(struct subtype *st, const char *id) { return udev_ccw_exists(st->name, id, false); } /* Check if a configuration exists for a CCW device with the specified @id. */ static bool ccw_st_exists_autoconf(struct subtype *st, const char *id) { return udev_ccw_exists(st->name, id, true); } static bool get_ids_cb(const char *file, void *data) { return ccw_is_id_valid(file, err_ignore) == EXIT_OK ? true : false; } /* Add all existing CCW devices attached to driver DRV to strlist LIST. If DRV * is NULL add all CCW device IDs. */ void ccw_get_ids(const char *drv, const char *mod, struct util_list *list) { char *path; cio_settle(0); path = path_get_ccw_devices(drv); if (mod) module_try_load_once(mod, path); misc_read_dir(path, list, get_ids_cb, NULL); free(path); } /* Add the IDs of all CCW devices existing in the active configuration to * strlist @ids. */ static void ccw_st_add_active_ids(struct subtype *st, struct util_list *ids) { struct ccw_subtype_data *data = st->data; ccw_get_ids(data->ccwdrv, data->mod, ids); } /* Add the IDs of all CCW devices for which a persistent configuration exists * to strlist @ids. */ static void ccw_st_add_persistent_ids(struct subtype *st, struct util_list *ids) { udev_get_device_ids(st->name, ids, false); } /* Add the IDs of all CCW devices for which a autoconf configuration exists * to strlist @ids. */ static void ccw_st_add_autoconf_ids(struct subtype *st, struct util_list *ids) { udev_get_device_ids(st->name, ids, true); } /* Read the configuration of the CCW device with the specified @id from the * active configuration and add the resulting data to @dev. */ static exit_code_t ccw_st_read_active(struct subtype *st, struct device *dev, read_scope_t scope) { struct ccw_subtype_data *data = st->data; const char *drv = data->ccwdrv, *id = dev->id; struct device_state *state = &dev->active; char *path; cio_settle(0); state->modified = 0; state->deconfigured = 0; state->definable = 0; path = path_get_ccw_device(drv, id); if (util_path_exists(path)) { state->exists = 1; device_read_active_settings(dev, scope); } else state->exists = 0; free(path); return EXIT_OK; } /* Read the configuration of the CCW device with the specified @id from the * persistent configuration and add the resulting data to @dev. */ static exit_code_t ccw_st_read_persistent(struct subtype *st, struct device *dev, read_scope_t scope) { return udev_ccw_read_device(dev, false); } /* Read the configuration of the CCW device with the specified @id from the * autoconf configuration and add the resulting data to @dev. */ static exit_code_t ccw_st_read_autoconf(struct subtype *st, struct device *dev, read_scope_t scope) { return udev_ccw_read_device(dev, true); } static int get_online(struct setting_list *list) { struct setting *s; if (!list) return -1; s = setting_list_find(list, "online"); if (!s) return -1; /* "force" cannot be read from online attribute but might be * in active list. */ if (strcmp(s->value, "force") == 0 || atoi(s->value) != 0) return 1; return 0; } static void ccw_get_online_attrib(struct attrib **attribs, int online, config_t config, struct attrib **a_ptr, const char **name_ptr, const char **value_ptr) { const char *name; const char *value = online ? "1" : "0"; struct attrib *a = NULL; /* Use safe_offline if available. */ if (!online && !force && config == config_active) { a = attrib_find(attribs, "safe_offline"); if (a) name = "safe_offline"; } /* Fall back to normal online. */ if (!a) a = attrib_find(attribs, "online"); if (a) name = a->name; else name = "online"; *a_ptr = a; *name_ptr = name; *value_ptr = value; } /* Apply the configuration found in @dev to the active configuration of the * corresponding device. */ static exit_code_t ccw_st_configure_active(struct subtype *st, struct device *dev) { cio_settle(0); return device_write_active_settings(dev); } /* Create a persistent configuration for the specified device @dev. */ static exit_code_t ccw_st_configure_persistent(struct subtype *st, struct device *dev) { return udev_ccw_write_device(dev, false); } /* Create a autoconf configuration for the specified device @dev. */ static exit_code_t ccw_st_configure_autoconf(struct subtype *st, struct device *dev) { return udev_ccw_write_device(dev, true); } static char *ccw_st_get_active_attrib_path(struct subtype *, struct device *, const char *); /** * ccw_st_deconfigure_active - Deconfigure device in active configuration set * @st: Subtype of target device * @dev: Target device * * Deconfigure device @dev in the active configuration set. Return %EXIT_OK on * success, an error code otherwise. */ static exit_code_t ccw_st_deconfigure_active(struct subtype *st, struct device *dev) { struct setting *s; char *path; exit_code_t rc; if (get_online(dev->active.settings) != 1) return EXIT_OK; /* Set the online attribute to 0. */ s = setting_list_apply(dev->active.settings, &ccwgroup_attr_online, ccwgroup_attr_online.name, "0"); path = ccw_st_get_active_attrib_path(st, dev, s->name); rc = setting_write(path, s); free(path); return rc; } /** * ccw_st_deconfigure_persistent - Deconfigure device in persistent * configuration set * @st: Subtype of target device * @dev: Target device * * Deconfigure device @dev in the persistent configuration set. Return %EXIT_OK * on success, an error code otherwise. */ static exit_code_t ccw_st_deconfigure_persistent(struct subtype *st, struct device *dev) { return udev_remove_rule(st->name, dev->id, false); } /** * ccw_st_deconfigure_autoconf - Deconfigure device in autoconf * configuration set * @st: Subtype of target device * @dev: Target device * * Deconfigure device @dev in the autoconf configuration set. Return %EXIT_OK * on success, an error code otherwise. */ static exit_code_t ccw_st_deconfigure_autoconf(struct subtype *st, struct device *dev) { return udev_remove_rule(st->name, dev->id, true); } /* Perform basic sanity checks. */ static exit_code_t ccw_st_check_pre_configure(struct subtype *st, struct device *dev, int prereq, config_t config) { char *drv; if (!SCOPE_ACTIVE(config) || dev->active.deconfigured) return EXIT_OK; drv = ccw_get_driver(dev->devid); if (!drv) { delayed_err("CCW device is not bound to a driver\n"); return EXIT_INVALID_CONFIG; } free(drv); return EXIT_OK; } /* Adjust the online state of the specified device @dev. */ static void ccw_st_online_set(struct subtype *st, struct device *dev, int online, config_t config) { struct attrib *a; const char *name, *value; ccw_get_online_attrib(st->dev_attribs, online, config, &a, &name, &value); if (SCOPE_ACTIVE(config)) setting_list_apply(dev->active.settings, a, name, value); if (SCOPE_PERSISTENT(config)) setting_list_apply(dev->persistent.settings, a, name, value); if (SCOPE_AUTOCONF(config)) setting_list_apply(dev->autoconf.settings, a, name, value); } /* Determine the online state of the specified CCW device (0=offline, 1=online, * -1=not set). */ static int ccw_st_online_get(struct subtype *st, struct device *dev, config_t config) { int act_online = 1, pers_online = 1, auto_online = 1; if (SCOPE_ACTIVE(config)) act_online = get_online(dev->active.settings); if (SCOPE_PERSISTENT(config)) pers_online = get_online(dev->persistent.settings); if (SCOPE_AUTOCONF(config)) auto_online = get_online(dev->autoconf.settings); return MIN(MIN(act_online, pers_online), auto_online); } /* Determine if the online state of the specified CCW device was specified */ static bool ccw_st_online_specified(struct subtype *st, struct device *dev, config_t config) { struct setting *s; if (SCOPE_ACTIVE(config) && dev->active.settings) { s = setting_list_find(dev->active.settings, "online"); if (s && s->specified) return true; } if (SCOPE_PERSISTENT(config) && dev->persistent.settings) { s = setting_list_find(dev->persistent.settings, "online"); if (s && s->specified) return true; } if (SCOPE_AUTOCONF(config) && dev->autoconf.settings) { s = setting_list_find(dev->autoconf.settings, "online"); if (s && s->specified) return true; } return false; } /* Add summary descriptions of operational problems with CCW device @id to * strlist @errors. */ static void ccw_st_add_errors(struct subtype *st, const char *id, struct util_list *errors) { char *path, *apath = NULL, *avail = NULL; path = ccw_get_dev_path(NULL, id); if (!path) return; if (!util_path_exists(path)) { strlist_add(errors, "CCW device %s does not exist", id); goto out; } apath = misc_asprintf("%s/availability", path); avail = misc_read_text_file(apath, 1, err_ignore); if (!avail) { strlist_add(errors, "CCW device %s is missing SysFS attributes", id); goto out; } if (strcmp(avail, "no device") == 0) { strlist_add(errors, "CCW device %s has been detached or " "removed", id); goto out; } if (strcmp(avail, "no path") == 0) { strlist_add(errors, "CCW device %s does not have any usable " "paths", id); goto out; } if (!util_path_exists("%s/driver", path)) { strlist_add(errors, "CCW device %s is not bound to a driver", id); goto out; } /* No known problem. */ out: free(avail); free(apath); free(path); } /* Add names of required kernel modules to strlist @modules. */ static void ccw_st_add_modules(struct subtype *st, struct device *dev, struct util_list *modules) { char *module; module = ccw_get_module(dev->devid); if (module) strlist_add_unique(modules, module); free(module); } /* Add struct devnodes to ptrlist @devnodes for each Linux device that is * provided by the CCW device with the specified ID. */ static void ccw_st_add_devnodes(struct subtype *st, const char *id, struct util_list *devnodes) { struct ccw_subtype_data *data = st->data; char *path; path = ccw_get_dev_path(data->ccwdrv, id); if (path) devnode_add_block_from_sysfs(devnodes, path); free(path); } /* Return a newly allocated CCW device ID of the device that provides the * specified @devnode. Return %NULL if no CCW device ID could be determined. */ static char *ccw_st_resolve_devnode(struct subtype *st, struct devnode *devnode) { char *link, *id = NULL, *curr, dummy; unsigned int cssid, sch_cssid, sch_ssid, sch_devno, dev_cssid, dev_ssid, dev_devno; if (devnode->type == NETDEV) return NULL; link = devnode_readlink(devnode); if (!link) return NULL; /* ../../devices/css0/0.0.0024/0.0.8000/block/dasdg/dasdg1/ */ curr = strstr(link, "/devices/"); if (!curr) goto out; /* /devices/css0/0.0.0024/0.0.8000/block/dasdg/dasdg1/ */ if (devnode->type == BLOCKDEV && sscanf(curr, "/devices/css%x/%x.%x.%x/%x.%x.%x/block%c", &cssid, &sch_cssid, &sch_ssid, &sch_devno, &dev_cssid, &dev_ssid, &dev_devno, &dummy) == 8 && dummy == '/') goto found; if (devnode->type == CHARDEV && sscanf(curr, "/devices/css%x/%x.%x.%x/%x.%x.%x/char%c", &cssid, &sch_cssid, &sch_ssid, &sch_devno, &dev_cssid, &dev_ssid, &dev_devno, &dummy) == 8 && dummy == '/') goto found; goto out; found: id = misc_asprintf("%x.%x.%04x", dev_cssid, dev_ssid, dev_devno); if (!subtype_device_exists_active(st, id)) { /* Subtype of CCW device doesn't match. */ free(id); id = NULL; } out: free(link); return id; } /* Return a newly allocated character string containing the path to the file * that implements a device attribute of the specified @name or %NULL if * such an attribute is not allowed. */ static char *ccw_st_get_active_attrib_path(struct subtype *st, struct device *dev, const char *name) { struct ccw_subtype_data *data = st->data; char *devpath, *path; devpath = path_get_ccw_device(data->ccwdrv, dev->id); path = misc_asprintf("%s/%s", devpath, name); free(devpath); return path; } /* The methods of this subtype assume that @data points to a * struct ccw_subtype_data. */ struct subtype ccw_subtype = { .super = &subtype_base, .exists_active = &ccw_st_exists_active, .exists_persistent = &ccw_st_exists_persistent, .exists_autoconf = &ccw_st_exists_autoconf, .add_active_ids = &ccw_st_add_active_ids, .add_persistent_ids = &ccw_st_add_persistent_ids, .add_autoconf_ids = &ccw_st_add_autoconf_ids, .read_active = &ccw_st_read_active, .read_persistent = &ccw_st_read_persistent, .read_autoconf = &ccw_st_read_autoconf, .configure_active = &ccw_st_configure_active, .configure_persistent = &ccw_st_configure_persistent, .configure_autoconf = &ccw_st_configure_autoconf, .deconfigure_active = &ccw_st_deconfigure_active, .deconfigure_persistent = &ccw_st_deconfigure_persistent, .deconfigure_autoconf = &ccw_st_deconfigure_autoconf, .check_pre_configure = &ccw_st_check_pre_configure, .online_set = &ccw_st_online_set, .online_get = &ccw_st_online_get, .online_specified = &ccw_st_online_specified, .add_errors = &ccw_st_add_errors, .add_modules = &ccw_st_add_modules, .add_devnodes = &ccw_st_add_devnodes, .resolve_devnode = &ccw_st_resolve_devnode, .get_active_attrib_path = &ccw_st_get_active_attrib_path, };