mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Enable user to specify that a device should be configured early, that is during the initial RAM-disk boot phase. This may be necessary, e.g. to override auto-configuration for a device which is also applied during that boot phase. It may also be used to manually specify devices that are required to access the root file system, such as networking devices. Users can mark devices as requiring early configuration by specifying a value of 1 for the newly added internal attribute zdev:early: # chzdev dasd-eckd 0.0.1234 -p zdev:early=1 This can be changed back by removing the attribute setting, or by setting the attribute value to 0: # chzdev dasd-eckd 0.0.1234 -p -r zdev:early or # chzdev dasd-eckd 0.0.1234 -p zdev:early=0 Signed-off-by: Peter Oberparleiter <oberpar@linux.vnet.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
573 lines
13 KiB
C
573 lines
13 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "attrib.h"
|
|
#include "ccw.h"
|
|
#include "ccwgroup.h"
|
|
#include "ctc.h"
|
|
#include "ctc_auto.h"
|
|
#include "device.h"
|
|
#include "devtype.h"
|
|
#include "internal.h"
|
|
#include "misc.h"
|
|
#include "namespace.h"
|
|
#include "path.h"
|
|
#include "setting.h"
|
|
|
|
#define DEVNAME "CTC device"
|
|
|
|
/*
|
|
* CTC device ID namespace methods.
|
|
*/
|
|
static exit_code_t ctc_parse_devid(struct ccwgroup_devid *devid_ptr,
|
|
const char *id, err_t err)
|
|
{
|
|
struct ccwgroup_devid devid;
|
|
const char *reason = NULL;
|
|
exit_code_t rc;
|
|
|
|
rc = ccwgroup_parse_devid(&devid, id, err);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (devid.num > CTC_NUM_DEVS) {
|
|
reason = "Too many CCW device IDs specified";
|
|
rc = EXIT_INVALID_ID;
|
|
} else if (devid_ptr)
|
|
*devid_ptr = devid;
|
|
|
|
if (reason) {
|
|
err_t_print(err, "Error in %s ID format: %s: %s\n", DEVNAME,
|
|
reason, id);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static exit_code_t ctc_parse_devid_range(struct ccwgroup_devid *from_ptr,
|
|
struct ccwgroup_devid *to_ptr,
|
|
const char *range, err_t err)
|
|
{
|
|
char *from_str, *to_str;
|
|
struct ccwgroup_devid from, to;
|
|
exit_code_t rc;
|
|
const char *reason = NULL;
|
|
|
|
/* Split range. */
|
|
from_str = misc_strdup(range);
|
|
to_str = strchr(from_str, '-');
|
|
if (!to_str) {
|
|
rc = EXIT_INVALID_ID;
|
|
reason = "Missing hyphen";
|
|
goto out;
|
|
}
|
|
*to_str = 0;
|
|
to_str++;
|
|
|
|
/* Parse range start end end ID. */
|
|
rc = ctc_parse_devid(&from, from_str, err);
|
|
if (rc)
|
|
goto out;
|
|
|
|
rc = ctc_parse_devid(&to, to_str, err);
|
|
if (rc)
|
|
goto out;
|
|
|
|
/* Only allow ranges on CCWGROUP devices specified as single ID. */
|
|
if (from.num != 1 || to.num != 1) {
|
|
rc = EXIT_INVALID_ID;
|
|
reason = "Ranges only supported on single CCW device IDs";
|
|
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 ctc_parse_devid_range_simple(struct ccwgroup_devid *from,
|
|
struct ccwgroup_devid *to,
|
|
const char *range)
|
|
{
|
|
if (ctc_parse_devid_range(from, to, range, err_ignore) == EXIT_OK)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static exit_code_t ctc_ns_is_id_valid(const char *id, err_t err)
|
|
{
|
|
return ctc_parse_devid(NULL, id, err);
|
|
}
|
|
|
|
static char *ctc_ns_normalize_id(const char *id)
|
|
{
|
|
struct ccwgroup_devid devid;
|
|
|
|
if (ctc_parse_devid(&devid, id, err_ignore) != EXIT_OK)
|
|
return NULL;
|
|
|
|
return ccwgroup_devid_to_str(&devid);
|
|
}
|
|
|
|
static void *ctc_ns_parse_id(const char *id, err_t err)
|
|
{
|
|
struct ccwgroup_devid *devid;
|
|
|
|
devid = misc_malloc(sizeof(struct ccwgroup_devid));
|
|
if (ctc_parse_devid(devid, id, err) != EXIT_OK) {
|
|
free(devid);
|
|
return NULL;
|
|
}
|
|
|
|
return devid;
|
|
}
|
|
|
|
static exit_code_t ctc_ns_is_id_range_valid(const char *range, err_t err)
|
|
{
|
|
return ctc_parse_devid_range(NULL, NULL, range, err);
|
|
}
|
|
|
|
static unsigned long ctc_ns_num_ids_in_range(const char *range)
|
|
{
|
|
struct ccwgroup_devid f, t;
|
|
|
|
if (!ctc_parse_devid_range_simple(&f, &t, range))
|
|
return 0;
|
|
|
|
if (f.devid[0].cssid != t.devid[0].cssid ||
|
|
f.devid[0].ssid != t.devid[0].ssid)
|
|
return 0;
|
|
|
|
if (f.devid[0].devno > t.devid[0].devno)
|
|
return 0;
|
|
|
|
return t.devid[0].devno - f.devid[0].devno + 1;
|
|
}
|
|
|
|
static void ctc_ns_range_start(struct ns_range_iterator *it, const char *range)
|
|
{
|
|
struct ccwgroup_devid from, to;
|
|
|
|
if (!ctc_parse_devid_range_simple(&from, &to, range)) {
|
|
memset(it, 0, sizeof(struct ns_range_iterator));
|
|
return;
|
|
}
|
|
|
|
it->devid = ccwgroup_copy_devid(&from);
|
|
it->devid_last = ccwgroup_copy_devid(&to);
|
|
it->id = ccwgroup_devid_to_str(it->devid);
|
|
}
|
|
|
|
static bool ctc_ns_is_id_blacklisted(const char *id)
|
|
{
|
|
struct ccwgroup_devid devid;
|
|
char *ccw_id;
|
|
unsigned int i;
|
|
bool result = false;
|
|
|
|
if (ctc_parse_devid(&devid, id, err_ignore) != EXIT_OK)
|
|
return false;
|
|
for (i = 0; i < devid.num; i++) {
|
|
ccw_id = ccw_devid_to_str(&devid.devid[i]);
|
|
result = ccw_is_id_blacklisted(ccw_id);
|
|
free(ccw_id);
|
|
if (result)
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void ctc_ns_unblacklist_id(const char *id)
|
|
{
|
|
struct ccwgroup_devid devid;
|
|
char *ccw_id;
|
|
unsigned int i;
|
|
|
|
if (ctc_parse_devid(&devid, id, err_ignore) != EXIT_OK)
|
|
return;
|
|
for (i = 0; i < devid.num; i++) {
|
|
ccw_id = ccw_devid_to_str(&devid.devid[i]);
|
|
if (ccw_is_id_blacklisted(ccw_id))
|
|
ccw_unblacklist_id(ccw_id);
|
|
free(ccw_id);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CTC device ID namespace.
|
|
*/
|
|
|
|
struct namespace ctc_namespace = {
|
|
.devname = DEVNAME,
|
|
.is_id_valid = ctc_ns_is_id_valid,
|
|
.is_id_similar = ccwgroup_is_id_similar,
|
|
.cmp_ids = ccwgroup_cmp_ids,
|
|
.normalize_id = ctc_ns_normalize_id,
|
|
.parse_id = ctc_ns_parse_id,
|
|
.cmp_parsed_ids = ccwgroup_cmp_parsed_ids,
|
|
.qsort_cmp = ccwgroup_qsort_cmp,
|
|
.is_id_range_valid = ctc_ns_is_id_range_valid,
|
|
.num_ids_in_range = ctc_ns_num_ids_in_range,
|
|
.is_id_in_range = ccwgroup_is_id_in_range,
|
|
.range_start = ctc_ns_range_start,
|
|
.range_next = ccwgroup_range_next,
|
|
|
|
/* Blacklist handling. */
|
|
.is_blacklist_active = ccw_is_blacklist_active,
|
|
.is_id_blacklisted = ctc_ns_is_id_blacklisted,
|
|
.is_id_range_blacklisted = ccw_is_id_range_blacklisted,
|
|
.unblacklist_id = ctc_ns_unblacklist_id,
|
|
.unblacklist_id_range = ccw_unblacklist_id_range,
|
|
.blacklist_persist = ccw_blacklist_persist,
|
|
};
|
|
|
|
/*
|
|
* CTC device attributes.
|
|
*/
|
|
|
|
static struct attrib ctc_attr_buffer = {
|
|
.name = "buffer",
|
|
.title = "Control maximum buffer size",
|
|
.desc =
|
|
"Specify the maximum buffer size used for a CTC interface. The value\n"
|
|
"must be in the range of <minimum MTU + header size> to\n"
|
|
"<maximum MTU + header size> where a header is typically 8 bytes\n"
|
|
"long. When this attribute is set, the MTU size of the interface\n"
|
|
"is also set accordingly.\n",
|
|
.defval = "32768",
|
|
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(584, 65535)),
|
|
.order_cmp = ccw_online_only_order_cmp,
|
|
.check = ccw_online_only_check,
|
|
};
|
|
|
|
static struct attrib ctc_attr_protocol = {
|
|
.name = "protocol",
|
|
.title = "Specify CTC interface protocol",
|
|
.desc =
|
|
"Specify the protocol to use for a CTC interface. The correct\n"
|
|
"protocol depends on the CTC connection peer:\n"
|
|
" 0: Non-z/OS peer such as a z/VM TCP service machine\n"
|
|
" 1: Linux peer\n"
|
|
" 3: z/OS peer\n"
|
|
" 4: MPC connection to VTAM\n",
|
|
.defval = "0",
|
|
.accept = ACCEPT_ARRAY(ACCEPT_RANGE(0, 1), ACCEPT_RANGE(3, 4)),
|
|
.order_cmp = ccw_offline_only_order_cmp,
|
|
.check = ccw_offline_only_check,
|
|
};
|
|
|
|
/*
|
|
* CTC subtype methods.
|
|
*/
|
|
|
|
/* Check if user attempts to modify an attribute of an online CTC device
|
|
* which can only be set while device is offline. */
|
|
static exit_code_t check_online_conflict(struct device *dev, config_t config)
|
|
{
|
|
struct attrib *attribs[] = {
|
|
&ctc_attr_protocol,
|
|
};
|
|
struct setting *online, *s;
|
|
unsigned int i;
|
|
exit_code_t rc;
|
|
|
|
/* All is well if:
|
|
* 1. We're not configuring the active configuration
|
|
* 2. online->actual == 0 or we don't know the actual value
|
|
* 3. online->modified && online->value == 0*/
|
|
if (!SCOPE_ACTIVE(config) || dev->active.deconfigured)
|
|
return EXIT_OK;
|
|
online = setting_list_find(dev->active.settings, ccw_attr_online.name);
|
|
if (!online)
|
|
return EXIT_OK;
|
|
if (!online->actual_value || atoi(online->actual_value) == 0)
|
|
return EXIT_OK;
|
|
if (online->modified && atoi(online->value) == 0)
|
|
return EXIT_OK;
|
|
|
|
rc = EXIT_OK;
|
|
for (i = 0; i < ARRAY_SIZE(attribs); i++) {
|
|
s = setting_list_find(dev->active.settings, attribs[i]->name);
|
|
if (!s || !s->modified)
|
|
continue;
|
|
delayed_warn("Attribute '%s' can only be changed while device "
|
|
"is offline\n", s->name);
|
|
rc = EXIT_INVALID_CONFIG;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static exit_code_t ctc_st_check_pre_configure(struct subtype *st,
|
|
struct device *dev,
|
|
int prereq, config_t config)
|
|
{
|
|
exit_code_t rc;
|
|
|
|
rc = check_online_conflict(dev, config);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
static exit_code_t ctc_st_is_definable(struct subtype *st, const char *id,
|
|
err_t err)
|
|
{
|
|
struct ccwgroup_subtype_data *data = st->data;
|
|
struct ccwgroup_devid devid;
|
|
exit_code_t rc;
|
|
|
|
rc = ccwgroup_parse_devid(&devid, id, err);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (subtype_device_exists_active(st, id))
|
|
return EXIT_OK;
|
|
|
|
if (devid.num == data->num_devs)
|
|
return ctc_auto_is_possible(&devid, err);
|
|
|
|
if (devid.num == 1)
|
|
return ctc_auto_get_devid(NULL, &devid.devid[0], err);
|
|
|
|
err_t_print(err, "Invalid number of CCW device IDs\n");
|
|
|
|
return EXIT_INVALID_ID;
|
|
}
|
|
|
|
/**
|
|
* device_detect_definable - Detect configuration of definable device
|
|
* @st: Device subtype
|
|
* @dev: Device
|
|
*
|
|
* Detect the full ID and default parameters for non-existing but definable
|
|
* device @dev and update active.definable. Return %EXIT_OK on success, or an
|
|
* error code otherwise.
|
|
*/
|
|
static exit_code_t ctc_st_detect_definable(struct subtype *st,
|
|
struct device *dev)
|
|
{
|
|
struct ccwgroup_devid *devid;
|
|
exit_code_t rc;
|
|
|
|
devid = dev->devid;
|
|
if (devid->num == 1) {
|
|
/* Detect possible group for this device. */
|
|
rc = ctc_auto_get_devid(devid, &devid->devid[0],
|
|
err_delayed_print);
|
|
if (rc) {
|
|
error("Auto-detection failed for %s %s\n"
|
|
"Please be sure to specify full CCWGROUP ID!\n",
|
|
st->devname, dev->id);
|
|
return rc;
|
|
}
|
|
free(dev->id);
|
|
dev->id = ccwgroup_devid_to_str(dev->devid);
|
|
}
|
|
|
|
dev->active.definable = 1;
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
static void ctc_st_add_definable_ids(struct subtype *st, struct util_list *ids)
|
|
{
|
|
ctc_auto_add_ids(ids);
|
|
}
|
|
|
|
/*
|
|
* CTC subtype.
|
|
*/
|
|
|
|
static struct ccwgroup_subtype_data ctc_data = {
|
|
.ccwgroupdrv = CTC_CCWGROUPDRV_NAME,
|
|
.ccwdrv = CTC_CCWDRV_NAME,
|
|
.rootdrv = CTC_ROOTDRV_NAME,
|
|
.mod = CTC_MOD_NAME,
|
|
.num_devs = CTC_NUM_DEVS,
|
|
};
|
|
|
|
static struct subtype ctc_subtype = {
|
|
.super = &ccwgroup_subtype,
|
|
.devtype = &ctc_devtype,
|
|
.name = "ctc",
|
|
.title = "Channel-To-Channel (CTC) and CTC-MPC network "
|
|
"devices",
|
|
.devname = DEVNAME,
|
|
.modules = STRING_ARRAY(CTC_MOD_NAME),
|
|
.namespace = &ctc_namespace,
|
|
.data = &ctc_data,
|
|
|
|
.dev_attribs = ATTRIB_ARRAY(
|
|
&ccw_attr_online,
|
|
&ctc_attr_buffer,
|
|
&ctc_attr_protocol,
|
|
&internal_attr_early,
|
|
),
|
|
.unknown_dev_attribs = 1,
|
|
.support_definable = 1,
|
|
|
|
.check_pre_configure = &ctc_st_check_pre_configure,
|
|
|
|
.is_definable = &ctc_st_is_definable,
|
|
.detect_definable = &ctc_st_detect_definable,
|
|
.add_definable_ids = &ctc_st_add_definable_ids,
|
|
};
|
|
|
|
/*
|
|
* CTC devtype methods.
|
|
*/
|
|
|
|
/* Clean up all resources used by devtype object. */
|
|
static void ctc_devtype_exit(struct devtype *dt)
|
|
{
|
|
setting_list_free(dt->active_settings);
|
|
setting_list_free(dt->persistent_settings);
|
|
}
|
|
|
|
static exit_code_t ctc_devtype_read_settings(struct devtype *dt,
|
|
config_t config)
|
|
{
|
|
/* No kernel or module parameters exist for the ctc device driver,
|
|
* but at least determine module loaded state. */
|
|
dt->active_settings = setting_list_new();
|
|
dt->persistent_settings = setting_list_new();
|
|
|
|
if (SCOPE_ACTIVE(config))
|
|
dt->active_exists = devtype_is_module_loaded(dt);
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
static exit_code_t ctc_devtype_write_settings(struct devtype *dt,
|
|
config_t config)
|
|
{
|
|
/* No kernel or module parameters exist for the ctc device driver. */
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
/*
|
|
* CTC devtype.
|
|
*/
|
|
|
|
struct devtype ctc_devtype = {
|
|
.name = "ctc",
|
|
.title = "", /* Only use subtypes. */
|
|
.devname = "CTC",
|
|
|
|
.subtypes = SUBTYPE_ARRAY(
|
|
&ctc_subtype,
|
|
),
|
|
|
|
.type_attribs = ATTRIB_ARRAY(
|
|
),
|
|
|
|
.exit = &ctc_devtype_exit,
|
|
|
|
.read_settings = &ctc_devtype_read_settings,
|
|
.write_settings = &ctc_devtype_write_settings,
|
|
};
|
|
|
|
/*
|
|
* Helper functions.
|
|
*/
|
|
|
|
static struct util_list *ctc_list;
|
|
|
|
static void add_query(struct util_list *list, const char *cmd)
|
|
{
|
|
char *vmcp, *curr, *next, **argv;
|
|
int argc;
|
|
struct ccw_devid devid;
|
|
|
|
/* Query real CTC adapters. */
|
|
vmcp = misc_read_cmd_output(cmd, 0, 1);
|
|
if (!vmcp)
|
|
return;
|
|
|
|
next = vmcp;
|
|
while ((curr = strsep(&next, "\n"))) {
|
|
line_split(curr, &argc, &argv);
|
|
if (argc >= 2 && ccw_parse_devid_simple(&devid, argv[1]))
|
|
ptrlist_add(list, ccw_copy_devid(&devid));
|
|
line_free(argc, argv);
|
|
}
|
|
|
|
free(vmcp);
|
|
}
|
|
|
|
/* Perform a CP QUERY CTCA ALL command and return a ptrlist of CCW device IDs
|
|
* for all found CTC devices. */
|
|
static struct util_list *query_ctc(void)
|
|
{
|
|
struct util_list *list;
|
|
char *cmd;
|
|
|
|
list = ptrlist_new();
|
|
|
|
/* Query real CTC adapters. */
|
|
cmd = misc_asprintf("%s query ctca all 2>/dev/null", PATH_VMCP);
|
|
add_query(list, cmd);
|
|
free(cmd);
|
|
|
|
/* Query virtual CTC adapters. */
|
|
cmd = misc_asprintf("%s query virtual ctca 2>/dev/null", PATH_VMCP);
|
|
add_query(list, cmd);
|
|
free(cmd);
|
|
|
|
return list;
|
|
}
|
|
|
|
/* Release all allocated resources. */
|
|
void ctc_exit(void)
|
|
{
|
|
ptrlist_free(ctc_list, 1);
|
|
}
|
|
|
|
/* Try to confirm that the specified CCW device ID refers to CTC device.
|
|
* Return %true it is a CTC device, %false if it cannot be confirmed. */
|
|
bool ctc_confirm(struct ccw_devid *devid)
|
|
{
|
|
struct ptrlist_node *p;
|
|
struct ccw_devid *d;
|
|
|
|
if (devid->cssid != 0 || devid->ssid != 0 || !is_zvm())
|
|
return false;
|
|
|
|
if (!ctc_list)
|
|
ctc_list = query_ctc();
|
|
|
|
util_list_iterate(ctc_list, p) {
|
|
d = p->ptr;
|
|
if (ccw_cmp_devids(devid, d) == 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|