mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Add support for managing virtual I/O ccw (virtio-ccw) devices in lszdev/chzdev. New zdev device type virtio. Each virtio device type (blk, net, gpu, vsock, etc.) is represented by a unique subtype of the virtio base type. If the virtio device type is recognized, then it will show up as virtio-blk or virtio-net, or whatever the virtio device type is. Otherwise, it will just show up as virtio-ccw. Fixes: https://github.com/ibm-s390-linux/s390-tools/issues/29 Reviewed-by: Peter Oberparleiter <oberpar@linux.ibm.com> Signed-off-by: Peter Jin <pjin@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
1893 lines
44 KiB
C
1893 lines
44 KiB
C
/*
|
|
* 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 <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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 + 1));
|
|
}
|
|
|
|
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 ccw_st_configure_persistent(st, dev);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
devnode_add_net_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;
|
|
}
|
|
|
|
/* Return a newly allocated character string containing the IPL device ID in
|
|
* case of CCW IPL. */
|
|
static char *ccw_st_get_ipldev_id(struct subtype *st)
|
|
{
|
|
char *type, *id = NULL;
|
|
|
|
type = path_read_text_file(1, err_ignore, PATH_IPL "/ipl_type");
|
|
if (strcmp(type, "ccw") != 0)
|
|
goto out;
|
|
|
|
id = path_read_text_file(1, err_ignore, PATH_IPL "/device");
|
|
if (!ccw_st_exists_active(st, id)) {
|
|
free(id);
|
|
id = NULL;
|
|
}
|
|
out:
|
|
free(type);
|
|
|
|
return id;
|
|
}
|
|
|
|
/* 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,
|
|
|
|
.get_ipldev_id = &ccw_st_get_ipldev_id,
|
|
};
|