mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
This commit is based on the s390-tools-1.39.0 version. Changes on top of s390-tools-1.39.0: - Add MIT license to all source files - Add LICENSE file - Transform REAMDE to README.md (markdown) - Add AUTHORS.md file - Add CONTRIBUTING.md file - Move changelog from README to CHANGELOG.md file Reviewed-by: Stefan Haberland <sth@linux.vnet.ibm.com> Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
428 lines
9.4 KiB
C
428 lines
9.4 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 "device.h"
|
|
#include "devtype.h"
|
|
#include "lcs.h"
|
|
#include "lcs_auto.h"
|
|
#include "misc.h"
|
|
#include "namespace.h"
|
|
#include "setting.h"
|
|
|
|
#define DEVNAME "LCS device"
|
|
|
|
/*
|
|
* LCS device ID namespace methods.
|
|
*/
|
|
static exit_code_t lcs_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 > LCS_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 lcs_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 = lcs_parse_devid(&from, from_str, err);
|
|
if (rc)
|
|
goto out;
|
|
|
|
rc = lcs_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 lcs_parse_devid_range_simple(struct ccwgroup_devid *from,
|
|
struct ccwgroup_devid *to,
|
|
const char *range)
|
|
{
|
|
if (lcs_parse_devid_range(from, to, range, err_ignore) == EXIT_OK)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static exit_code_t lcs_ns_is_id_valid(const char *id, err_t err)
|
|
{
|
|
return lcs_parse_devid(NULL, id, err);
|
|
}
|
|
|
|
static char *lcs_ns_normalize_id(const char *id)
|
|
{
|
|
struct ccwgroup_devid devid;
|
|
|
|
if (lcs_parse_devid(&devid, id, err_ignore) != EXIT_OK)
|
|
return NULL;
|
|
|
|
return ccwgroup_devid_to_str(&devid);
|
|
}
|
|
|
|
static void *lcs_ns_parse_id(const char *id, err_t err)
|
|
{
|
|
struct ccwgroup_devid *devid;
|
|
|
|
devid = misc_malloc(sizeof(struct ccwgroup_devid));
|
|
if (lcs_parse_devid(devid, id, err) != EXIT_OK) {
|
|
free(devid);
|
|
return NULL;
|
|
}
|
|
|
|
return devid;
|
|
}
|
|
|
|
static exit_code_t lcs_ns_is_id_range_valid(const char *range, err_t err)
|
|
{
|
|
return lcs_parse_devid_range(NULL, NULL, range, err);
|
|
}
|
|
|
|
static unsigned long lcs_ns_num_ids_in_range(const char *range)
|
|
{
|
|
struct ccwgroup_devid f, t;
|
|
|
|
if (!lcs_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 lcs_ns_range_start(struct ns_range_iterator *it, const char *range)
|
|
{
|
|
struct ccwgroup_devid from, to;
|
|
|
|
if (!lcs_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 lcs_ns_is_id_blacklisted(const char *id)
|
|
{
|
|
struct ccwgroup_devid devid;
|
|
char *ccw_id;
|
|
unsigned int i;
|
|
bool result = false;
|
|
|
|
if (lcs_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 lcs_ns_unblacklist_id(const char *id)
|
|
{
|
|
struct ccwgroup_devid devid;
|
|
char *ccw_id;
|
|
unsigned int i;
|
|
|
|
if (lcs_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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* LCS device ID namespace.
|
|
*/
|
|
|
|
struct namespace lcs_namespace = {
|
|
.devname = DEVNAME,
|
|
.is_id_valid = lcs_ns_is_id_valid,
|
|
.is_id_similar = ccwgroup_is_id_similar,
|
|
.cmp_ids = ccwgroup_cmp_ids,
|
|
.normalize_id = lcs_ns_normalize_id,
|
|
.parse_id = lcs_ns_parse_id,
|
|
.cmp_parsed_ids = ccwgroup_cmp_parsed_ids,
|
|
.qsort_cmp = ccwgroup_qsort_cmp,
|
|
.is_id_range_valid = lcs_ns_is_id_range_valid,
|
|
.num_ids_in_range = lcs_ns_num_ids_in_range,
|
|
.is_id_in_range = ccwgroup_is_id_in_range,
|
|
.range_start = lcs_ns_range_start,
|
|
.range_next = ccwgroup_range_next,
|
|
|
|
/* Blacklist handling. */
|
|
.is_blacklist_active = ccw_is_blacklist_active,
|
|
.is_id_blacklisted = lcs_ns_is_id_blacklisted,
|
|
.is_id_range_blacklisted = ccw_is_id_range_blacklisted,
|
|
.unblacklist_id = lcs_ns_unblacklist_id,
|
|
.unblacklist_id_range = ccw_unblacklist_id_range,
|
|
.blacklist_persist = ccw_blacklist_persist,
|
|
};
|
|
|
|
/*
|
|
* LCS device attributes.
|
|
*/
|
|
|
|
static struct attrib lcs_attr_lancmd_timeout = {
|
|
.name = "lancmd_timeout",
|
|
.title = "Modify LAN command timeout",
|
|
.desc =
|
|
"Specify the time in seconds that the LCS driver waits for a reply\n"
|
|
"after issuing a LAN command to the LAN adapter.\n",
|
|
.defval = "5",
|
|
.accept = ACCEPT_ARRAY(ACCEPT_NUM_GE(1)),
|
|
};
|
|
|
|
static struct attrib lcs_attr_recover = {
|
|
.name = "recover",
|
|
.title = "Trigger device recovery",
|
|
.desc =
|
|
"Write '1' to this attribute to restart the recovery process for the\n"
|
|
"QETH device.\n",
|
|
.accept = ACCEPT_ARRAY(ACCEPT_NUM(1)),
|
|
.writeonly = 1,
|
|
.activeonly = 1,
|
|
};
|
|
|
|
/*
|
|
* LCS subtype methods.
|
|
*/
|
|
|
|
static exit_code_t lcs_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 lcs_auto_is_possible(&devid, err);
|
|
|
|
if (devid.num == 1)
|
|
return lcs_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 lcs_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 = lcs_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 lcs_st_add_definable_ids(struct subtype *st, struct util_list *ids)
|
|
{
|
|
lcs_auto_add_ids(ids);
|
|
}
|
|
|
|
/*
|
|
* LCS subtype.
|
|
*/
|
|
|
|
static struct ccwgroup_subtype_data lcs_data = {
|
|
.ccwgroupdrv = LCS_CCWGROUPDRV_NAME,
|
|
.ccwdrv = LCS_CCWDRV_NAME,
|
|
.rootdrv = LCS_ROOTDRV_NAME,
|
|
.mod = LCS_MOD_NAME,
|
|
.num_devs = LCS_NUM_DEVS,
|
|
};
|
|
|
|
static struct subtype lcs_subtype = {
|
|
.super = &ccwgroup_subtype,
|
|
.devtype = &lcs_devtype,
|
|
.name = "lcs",
|
|
.title = "LAN-Channel-Station (LCS) network devices",
|
|
.devname = DEVNAME,
|
|
.modules = STRING_ARRAY(LCS_MOD_NAME),
|
|
.namespace = &lcs_namespace,
|
|
.data = &lcs_data,
|
|
|
|
.dev_attribs = ATTRIB_ARRAY(
|
|
&ccw_attr_online,
|
|
&lcs_attr_lancmd_timeout,
|
|
&lcs_attr_recover,
|
|
),
|
|
.unknown_dev_attribs = 1,
|
|
.support_definable = 1,
|
|
|
|
.is_definable = &lcs_st_is_definable,
|
|
.detect_definable = &lcs_st_detect_definable,
|
|
.add_definable_ids = &lcs_st_add_definable_ids,
|
|
};
|
|
|
|
/*
|
|
* LCS devtype methods.
|
|
*/
|
|
|
|
/* Clean up all resources used by devtype object. */
|
|
static void lcs_devtype_exit(struct devtype *dt)
|
|
{
|
|
setting_list_free(dt->active_settings);
|
|
setting_list_free(dt->persistent_settings);
|
|
}
|
|
|
|
static exit_code_t lcs_devtype_read_settings(struct devtype *dt,
|
|
config_t config)
|
|
{
|
|
/* No kernel or module parameters exist for the lcs 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 lcs_devtype_write_settings(struct devtype *dt,
|
|
config_t config)
|
|
{
|
|
/* No kernel or module parameters exist for the lcs device driver. */
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
/*
|
|
* LCS devtype.
|
|
*/
|
|
|
|
struct devtype lcs_devtype = {
|
|
.name = "lcs",
|
|
.title = "", /* Only use subtypes. */
|
|
.devname = "LCS",
|
|
|
|
.subtypes = SUBTYPE_ARRAY(
|
|
&lcs_subtype,
|
|
),
|
|
|
|
.type_attribs = ATTRIB_ARRAY(
|
|
),
|
|
|
|
.exit = &lcs_devtype_exit,
|
|
|
|
.read_settings = &lcs_devtype_read_settings,
|
|
.write_settings = &lcs_devtype_write_settings,
|
|
};
|