From 8cea23d63c2ca565f1ee9d29ced8e28602d91c26 Mon Sep 17 00:00:00 2001 From: "Jason J. Herne" Date: Tue, 17 Nov 2020 16:13:20 -0500 Subject: [PATCH] ipl-tools: Add nvme device support to lsreipl/chreipl Support is added to lsreipl to detect the nvme reipl type and list the appropriate parameters. Support is added to chreipl to specify and populate sysfs reipl entries with nvme device targets. Signed-off-by: Jason J. Herne --- ipl_tools/Makefile | 3 +- ipl_tools/cmd_chreipl.c | 153 ++++++++++++++++++++++++++++++++++-- ipl_tools/cmd_lsreipl.c | 33 ++++++++ ipl_tools/ipl_tools.h | 11 +++ ipl_tools/man/chreipl.8 | 53 ++++++++++++- ipl_tools/nvme.c | 169 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 414 insertions(+), 8 deletions(-) create mode 100644 ipl_tools/nvme.c diff --git a/ipl_tools/Makefile b/ipl_tools/Makefile index fe09041..d5c07c6 100644 --- a/ipl_tools/Makefile +++ b/ipl_tools/Makefile @@ -6,7 +6,7 @@ all: chreipl lsreipl chshut lsshut libs = $(rootdir)/libutil/libutil.a -objects = main.o ccw.o fcp.o system.o shutdown.o \ +objects = main.o ccw.o fcp.o nvme.o system.o shutdown.o \ cmd_lsshut.o cmd_chshut.o cmd_lsreipl.o cmd_chreipl.o proc.o chreipl: $(objects) $(libs) @@ -40,4 +40,3 @@ install: all $(DESTDIR)$(MANDIR)/man8 .PHONY: all install clean - diff --git a/ipl_tools/cmd_chreipl.c b/ipl_tools/cmd_chreipl.c index 50cccf4..f511568 100644 --- a/ipl_tools/cmd_chreipl.c +++ b/ipl_tools/cmd_chreipl.c @@ -28,12 +28,14 @@ enum target_type { TT_FCP, TT_NSS, TT_NODE, + TT_NVME, }; enum reipl_type { REIPL_FCP, REIPL_CCW, - REIPL_NSS + REIPL_NSS, + REIPL_NVME, }; static const char *const usage_chreipl = @@ -41,6 +43,7 @@ static const char *const usage_chreipl = "\n" " chreipl [ccw] [-d] [OPTIONS]\n" " chreipl [fcp] [-d] [-w] [-l] [OPTIONS]\n" +" chreipl nvme [-i] [-s] [OPTIONS]\n" " chreipl [node] [OPTIONS]\n" " chreipl nss [-n] [OPTIONS]\n" " chreipl [-h] [-v]\n" @@ -48,6 +51,7 @@ static const char *const usage_chreipl = "The following re-IPL targets are supported:\n" " ccw IPL from CCW device\n" " fcp IPL from FCP device\n" +" nvme IPL from NVME device\n" " nss IPL from NSS\n" " node IPL from device specified by device node or directory\n" "\n" @@ -68,6 +72,12 @@ static const char *const usage_chreipl = " -b, --bootprog Bootprog specification\n" " -L, --loadparm Loadparm specification\n" "\n" +"Options for nvme target:\n" +" -i, --fid PCI Function ID of NVME IPL device (hex)\n" +" -s --nsid Namespace ID of NVME IPL device (decimal, default 1)\n" +" -b, --bootprog Bootprog specification\n" +" -L, --loadparm Loadparm specification\n" +"\n" "Options for nss target:\n" " -n, --name Identifier of the NSS\n" "\n" @@ -84,6 +94,10 @@ static struct locals { char lun[20]; /* 18 character +0x" */ int lun_set; char busid[10]; /* Bus ID e.g. 0.0.4711 */ + int fid_set; + char fid[FID_MAX_LEN]; + int nsid_set; + char nsid[11]; /* 10 decimal chars + null */ int busid_set; char dev[15]; /* Device (e.g. dasda) */ int dev_set; @@ -92,10 +106,10 @@ static struct locals { char bootparms[4096]; int bootparms_set; int force_set; - enum target_type target_type; /* CCW, FCP, NSS or NODE */ + enum target_type target_type; /* CCW, FCP, NVME, NSS or NODE */ int target_type_set; int target_type_auto_mode; - enum reipl_type reipl_type; /* CCW, FCP, NSS */ + enum reipl_type reipl_type; /* CCW, FCP, NVME, NSS */ } l; static void __noreturn print_usage_chreipl_exit(void) @@ -226,6 +240,34 @@ static void set_wwpn(const char *wwpn) l.wwpn_set = 1; } +static void set_nvme_nsid(const char *nsid) +{ + unsigned long long nsid_tmp; + char *endptr; + + nsid_tmp = strtoull(nsid, &endptr, 10); + if (*endptr) + ERR_EXIT("NSID \"%s\" is not a decimal number", nsid); + snprintf(l.nsid, sizeof(l.nsid), "%08llu", nsid_tmp); + l.nsid_set = 1; +} + +static void set_nvme_fid(const char *fid) +{ + unsigned long long fid_tmp; + char *endptr; + + fid_tmp = strtoull(fid, &endptr, 16); + if (*endptr) + ERR_EXIT("FID \"%s\" is not a hexadecimal number", fid); + snprintf(l.fid, sizeof(l.fid), "0x%08llx", fid_tmp); + l.fid_set = 1; + + /* nsid defaults to 1, if not already set */ + if (!l.nsid_set) + set_nvme_nsid("1"); +} + static void parse_fcp_args(char *nargv[], int nargc) { /* @@ -245,6 +287,28 @@ static void parse_fcp_args(char *nargv[], int nargc) set_lun(nargv[2]); } +static void parse_nvme_args(char *nargv[], int nargc) +{ + /* + * we might be called like this: + * chreipl nvme 0x13 1 + */ + if (l.busid_set || l.fid_set || l.nsid_set || l.dev_set) + ERR_EXIT("Use either options or positional parameters"); + if (nargc > 2) + ERR_EXIT("Too many arguments specified for \"nvme\" re-IPL " + "type"); + else if (nargc < 1) + ERR_EXIT("The \"nvme\" re-IPL type requires function id, and " + "optional namespace id"); + set_nvme_fid(nargv[0]); + + if (nargc == 2) + set_nvme_nsid(nargv[1]); + else + set_nvme_nsid("1"); +} + static void parse_ccw_args(char *nargv[], int nargc) { /* @@ -285,6 +349,13 @@ static void dev_from_part(char *dev_name) dev_name[i] = 0; } +static void dev_from_part_nvme(char *dev_name) +{ + char *delim = strrchr(dev_name, 'p'); + if (delim) + *delim = 0; +} + static int set_reipl_type(const char *dev_name) { if (strncmp(dev_name, "dasd", strlen("dasd")) == 0 || @@ -292,11 +363,18 @@ static int set_reipl_type(const char *dev_name) l.reipl_type = REIPL_CCW; else if (strncmp(dev_name, "sd", strlen("sd")) == 0) l.reipl_type = REIPL_FCP; + else if (strncmp(dev_name, "nvme", strlen("nvme")) == 0) + l.reipl_type = REIPL_NVME; else return -1; util_strlcpy(l.dev, dev_name, sizeof(l.dev)); - dev_from_part(l.dev); + + if (l.reipl_type == REIPL_NVME) + dev_from_part_nvme(l.dev); + else + dev_from_part(l.dev); + l.dev_set = 1; return 0; } @@ -399,6 +477,9 @@ static void parse_pos_args(char *nargv[], int nargc) case TT_FCP: parse_fcp_args(nargv, nargc); break; + case TT_NVME: + parse_nvme_args(nargv, nargc); + break; case TT_CCW: parse_ccw_args(nargv, nargc); break; @@ -420,6 +501,14 @@ static void check_fcp_opts(void) "and LUN"); } +static void check_nvme_opts(void) +{ + if (l.nss_name_set || l.wwpn_set || l.lun_set || l.busid_set) + ERR_EXIT("Invalid option for \"nvme\" target specified"); + if (!(l.fid_set && l.nsid_set)) + ERR_EXIT("The \"nvme\" target requires FID, and optional NSID"); +} + static void check_ccw_opts(void) { if (l.bootprog_set || l.lun_set || l.wwpn_set || l.nss_name_set) @@ -469,6 +558,8 @@ static void parse_chreipl_options(int argc, char *argv[]) { "device", required_argument, NULL, 'd' }, { "lun", required_argument, NULL, 'l' }, { "wwpn", required_argument, NULL, 'w' }, + { "fid", required_argument, NULL, 'i' }, + { "nsid", required_argument, NULL, 's' }, { "loadparm", required_argument, NULL, 'L' }, { "name", required_argument, NULL, 'n' }, { "bootparms", required_argument, NULL, 'p' }, @@ -476,7 +567,7 @@ static void parse_chreipl_options(int argc, char *argv[]) { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; - static const char optstr[] = "hcd:vw:l:fL:b:n:p:"; + static const char optstr[] = "hcd:vw:l:fL:b:n:p:i:s:"; /* dont run without any argument */ if (argc == 1) @@ -488,6 +579,8 @@ static void parse_chreipl_options(int argc, char *argv[]) set_target_type(TT_CCW, 0); else if (strcmp(argv[1], "nss") == 0) set_target_type(TT_NSS, 0); + else if (strcmp(argv[1], "nvme") == 0) + set_target_type(TT_NVME, 0); else if (strcmp(argv[1], "node") == 0) set_target_type(TT_NODE, 0); else @@ -500,9 +593,15 @@ static void parse_chreipl_options(int argc, char *argv[]) case 'd': set_device(optarg); break; + case 'i': + set_nvme_fid(optarg); + break; case 'l': set_lun(optarg); break; + case 's': + set_nvme_nsid(optarg); + break; case 'w': set_wwpn(optarg); break; @@ -650,6 +749,40 @@ static void chreipl_fcp(void) print_fcp(0, 0); } +static void chreipl_nvme(void) +{ + check_nvme_opts(); + + if (!nvme_is_device(l.fid, l.nsid) && !l.force_set) { + ERR_EXIT("Could not find NVME device with fid %s and nsid %s", + l.fid, l.nsid); + } + check_exists("reipl/nvme/fid", "\"nvme\" re-IPL target"); + + if (l.bootparms_set && strlen(l.bootparms) > BOOTPARMS_FCP_MAX) { + ERR_EXIT("Maximum boot parameter length exceeded (%zu/%u)", + strlen(l.bootparms), BOOTPARMS_FCP_MAX); + } + + write_str_optional(l.loadparm, "reipl/nvme/loadparm", l.loadparm_set, + "loadparm"); + write_str_optional(l.bootparms, "reipl/nvme/scp_data", l.bootparms_set, + "boot parameters"); + write_str(l.fid, "reipl/nvme/fid"); + write_str(l.nsid, "reipl/nvme/nsid"); + /* + * set the boot record logical block address. Master boot + * record. It is always 0 for Linux + */ + write_str("0", "reipl/nvme/br_lba"); + if (!l.bootprog_set) + sprintf(l.bootprog, "0"); + write_str(l.bootprog, "reipl/nvme/bootprog"); + write_str("nvme", "reipl/reipl_type"); + + print_nvme(0, 0); +} + static void chreipl_nss(void) { check_nss_opts(); @@ -690,6 +823,13 @@ static void chreipl_node(void) l.busid_set = 1; chreipl_fcp(); break; + case REIPL_NVME: + nvme_fid_get(l.dev, l.fid); + l.fid_set = 1; + nvme_nsid_get(l.dev, l.nsid); + l.nsid_set = 1; + chreipl_nvme(); + break; default: ERR_EXIT("Internal error: chreipl_node"); } @@ -705,6 +845,9 @@ void cmd_chreipl(int argc, char *argv[]) case TT_FCP: chreipl_fcp(); break; + case TT_NVME: + chreipl_nvme(); + break; case TT_NSS: chreipl_nss(); break; diff --git a/ipl_tools/cmd_lsreipl.c b/ipl_tools/cmd_lsreipl.c index c906f83..8018c83 100644 --- a/ipl_tools/cmd_lsreipl.c +++ b/ipl_tools/cmd_lsreipl.c @@ -81,6 +81,35 @@ void print_fcp(int show_ipl, int dump) print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data"); } +void print_nvme(int show_ipl, int dump) +{ + char *dir = show_ipl ? "ipl" : "reipl/nvme"; + char *path_bootparms = show_ipl ? "/sys/firmware/ipl/scp_data" : + "/sys/firmware/reipl/nvme/scp_data"; + char *path_loadparm = show_ipl ? "/sys/firmware/ipl/loadparm" : + "/sys/firmware/reipl/nvme/loadparm"; + char loadparm[9], loadparm_path[PATH_MAX]; + + if (dump) + printf("%-12s nvme_dump\n", get_ipl_banner(show_ipl)); + else + printf("%-12s nvme\n", get_ipl_banner(show_ipl)); + + print_fw_str("FID: %s\n", dir, "fid"); + print_fw_str("NSID: %s\n", dir, "nsid"); + print_fw_str("bootprog: %s\n", dir, "bootprog"); + print_fw_str("br_lba: %s\n", dir, "br_lba"); + if (access(path_loadparm, R_OK) == 0) { + sprintf(loadparm_path, "%s/%s", dir, "loadparm"); + read_fw_str(loadparm, loadparm_path, sizeof(loadparm)); + if (strcmp(loadparm, " ") == 0) + loadparm[0] = 0; + printf("Loadparm: \"%s\"\n", loadparm); + } + if (access(path_bootparms, R_OK) == 0) + print_fw_str("Bootparms: \"%s\"\n", dir, "scp_data"); +} + void print_ccw(int show_ipl) { char loadparm[9], loadparm_path[PATH_MAX]; @@ -149,6 +178,10 @@ void cmd_lsreipl(int argc, char *argv[]) print_fcp(l.ipl_set, 0); else if (strcmp(reipl_type_str, "fcp_dump") == 0) print_fcp(l.ipl_set, 1); + else if (strcmp(reipl_type_str, "nvme") == 0) + print_nvme(l.ipl_set, 0); + else if (strcmp(reipl_type_str, "nvme_dump") == 0) + print_nvme(l.ipl_set, 1); else if (strcmp(reipl_type_str, "ccw") == 0) print_ccw(l.ipl_set); else if (strcmp(reipl_type_str, "nss") == 0) diff --git a/ipl_tools/ipl_tools.h b/ipl_tools/ipl_tools.h index 50d0b74..576d33a 100644 --- a/ipl_tools/ipl_tools.h +++ b/ipl_tools/ipl_tools.h @@ -43,6 +43,7 @@ extern void cmd_chreipl(int argc, char *argv[]); extern void print_ccw(int show_ipl); extern void print_fcp(int show_ipl, int dump); +extern void print_nvme(int show_ipl, int dump); extern void print_nss(int show_ipl); /* @@ -70,6 +71,16 @@ extern void fcp_lun_get(const char *device, char *lun); extern void fcp_wwpn_get(const char *device, char *wwpn); extern void fcp_busid_get(const char *device, char *devno); +/* + * NVME + */ +#define FID_MAX_LEN 11 /* 8 characters + 0x + null */ +#define NVME_PATH_MAX (PATH_MAX + NAME_MAX + 1) + +extern void nvme_fid_get(const char *device, char *fid); +extern void nvme_nsid_get(const char *device, char *nsid); +int nvme_is_device(char *fid_str, char *nsid_str); + /* * CCW */ diff --git a/ipl_tools/man/chreipl.8 b/ipl_tools/man/chreipl.8 index 9adb93a..e0c0f52 100644 --- a/ipl_tools/man/chreipl.8 +++ b/ipl_tools/man/chreipl.8 @@ -39,6 +39,9 @@ Specify a DASD CCW device for reboot .RB "- " fcp : Specify a FCP device for reboot .TP +.RB "- " nvme : +Specify an NVMe device for reboot +.TP .RB "- " nss : Specify a named saved system (NSS) for reboot .TP @@ -118,6 +121,11 @@ WWPN 0x500507630300c562, and LUN 0x401040b300000000. In addition to that append kernel parameter "mem=" to restrict memory to 512 MB: \fB# chreipl 0.0.1700 0x500507630300c562 0x401040b300000000 -p "mem=512M"\fP + +4. Next time reboot from the NVMe device with function id 0x13, namespace 1: + +\fB# chreipl nvme 0x13 1 + .SH ccw Use the ccw re-IPL target for DASD devices that are accessed by the hardware using channel command word (CCW) channels. @@ -192,6 +200,44 @@ use options instead of positional parameters: .br \fB# chreipl fcp -d 0.0.1700 -w 0x5005076... -l 0x401040b3... -b 2\fP +.SH nvme +Use the nvme re-IPL target for specifying an NVMe disk for reboot. +.TP +.BR "\-i" " or " "\-\-fid" +PCI Function ID of NVME IPL device (hex). + +.TP +.BR "\-s" " or " "\-\-nsid" +Namespace ID of the NVME IPL device (decimal, default 1). + +.TP +.BR "\-b" " or " "\-\-bootprog" +Specifies an entry in the boot configuration by defining the IPL boot +program selector. If omitted, '0' will be used. + +.TP +.BR "\-L" " or " "\-\-loadparm" +The loadparm for the nvme re-IPL target is not used to control the boot +configuration that is defined by the +.BR zipl (8) +boot menu. Instead it can be used to control higher level boot loaders +like GRUB. For more details refer to distribution specific documentation. + +.PP +\fBExamples:\fP +.br + +1. Next time reboot from the NVMe disk with function-id 0x13 and namespace 1: +.br + +\fB# chreipl nvme 0x13 1\fP +.br + +2. Use same configuration as (1) but choose boot program selector 2 and +use options instead of positional parameters: +.br + +\fB# chreipl nvme -i 0x13 -s 1 -b 2\fP .SH nss Use the nss re-IPL target to specify z/VM named saved systems (NSS) for reboot. @@ -206,7 +252,7 @@ Use the NSS named LINUX1 for the next reboot: \fB# chreipl nss LINUX1\fP .SH node -You can identify DASD or SCSI re-IPL devices indirectly through a device +You can identify DASD or SCSI, or NVMe re-IPL devices indirectly through a device node or directory. The chreipl tool then determines the information that you would otherwise have to specify with the ccw or fcp target. .PP @@ -229,6 +275,11 @@ that you would otherwise have to specify with the ccw or fcp target. \fB# chreipl node /mnt/boot\fP +4. Next time reboot from the NVMe device represented by /dev/nvme0n1 +.br + +\fB# chreipl node /dev/nvme0n1\fP + .SH SEE ALSO .BR lsreipl (8), .BR zipl (8), diff --git a/ipl_tools/nvme.c b/ipl_tools/nvme.c new file mode 100644 index 0000000..2a95972 --- /dev/null +++ b/ipl_tools/nvme.c @@ -0,0 +1,169 @@ +/* + * ipl_tools - Linux for System z reipl and shutdown tools + * + * NVMe device functions + * + * Copyright IBM Corp. 2020 + * + * 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_libc.h" +#include "lib/util_file.h" +#include "ipl_tools.h" + +/* + * Return the fid of a device + */ +void nvme_fid_get(const char *device, char *fid) +{ + char path[PATH_MAX], buf[FID_MAX_LEN]; + + snprintf(path, PATH_MAX, "/sys/block/%s/device/device/function_id", + device); + if (util_file_read_line(buf, FID_MAX_LEN, path)) + ERR_EXIT_ERRNO("Could not read from \"%s\"", path); + + util_strlcpy(fid, buf, FID_MAX_LEN); +} +/* + * Return the nsid of a device + */ +void nvme_nsid_get(const char *device, char *nsid) +{ + char path[PATH_MAX], buf[FID_MAX_LEN]; + + snprintf(path, PATH_MAX, "/sys/block/%s/nsid", device); + if (util_file_read_line(buf, FID_MAX_LEN, path)) + ERR_EXIT_ERRNO("Could not read from \"%s\"", path); + + util_strlcpy(nsid, buf, FID_MAX_LEN); +} + +static int next_entry(DIR *dir, char *in_path, char *out_path, + unsigned char entry_type) +{ + struct dirent *dirent; + char temp_path[NVME_PATH_MAX]; + + while ((dirent = readdir(dir)) != NULL) { + if (strcmp(dirent->d_name, ".") == 0 || + strcmp(dirent->d_name, "..") == 0 || + dirent->d_type != entry_type) + continue; + + /* Resolve the symlink, if needed */ + if (dirent->d_type == DT_LNK) { + snprintf(temp_path, sizeof(temp_path), "%s/%s", in_path, + dirent->d_name); + if (!realpath(temp_path, out_path)) + ERR_EXIT_ERRNO("Could not resolve link %s", + temp_path); + return 1; + } + + snprintf(out_path, NVME_PATH_MAX, "%s/%s", in_path, + dirent->d_name); + return 1; + } + return 0; +} + +static int nvme_getdev_by_fid(char *fidstr, char *devpath) +{ + char temp_path[PATH_MAX+19], real_path[PATH_MAX]; + char *sys_path = "/sys/class/nvme"; + u_int64_t target_fid, curfid; + DIR *dir; + char *end; + int rc = -1; + + target_fid = strtoul(fidstr, &end, 16); + if (*end) + ERR_EXIT("Invalid function_id given %s", fidstr); + + dir = opendir(sys_path); + if (!dir) + ERR_EXIT("Could not open %s", sys_path); + + errno = 0; + while (next_entry(dir, sys_path, real_path, DT_LNK)) { + snprintf(temp_path, sizeof(temp_path), "%s/%s", real_path, + "device/function_id"); + if (access(temp_path, F_OK)) + continue; + + if (util_file_read_ul(&curfid, 16, temp_path)) + ERR_EXIT("Invalid function_id found in %s", temp_path); + + if (curfid == target_fid) { + strncpy(devpath, real_path, PATH_MAX); + rc = 0; + break; + } + } + + closedir(dir); + return rc; +} + +static int nvme_getdev_by_nsid(char *nsid_str, char *path, char *dev_path) +{ + char full_path[NVME_PATH_MAX+1], nsid_path[sizeof(full_path)+5]; + char *end; + u_int64_t nsid, curnsid; + DIR *dir; + + nsid = strtoul(nsid_str, &end, 10); + if (*end) + ERR_EXIT_ERRNO("Invalid namespace id given %s", nsid_str); + + dir = opendir(path); + if (!dir) + ERR_EXIT_ERRNO("Could not open %s", path); + + errno = 0; + while (next_entry(dir, path, full_path, DT_DIR)) { + snprintf(nsid_path, sizeof(nsid_path), "%s/%s", full_path, + "nsid"); + if (access(nsid_path, F_OK)) + continue; + + if (util_file_read_ul(&curnsid, 10, nsid_path)) + ERR_EXIT("Invalid namespace id found in %s", nsid_path); + + if (curnsid == nsid) { + strncpy(dev_path, full_path, NVME_PATH_MAX+1); + closedir(dir); + return 0; + } + } + closedir(dir); + return -1; +} + +static int nvme_getdev(char *fid_str, char *nsid_str, char *dev_path) +{ + char path_tmp[NVME_PATH_MAX]; + + if (nvme_getdev_by_fid(fid_str, path_tmp)) + return -1; + + return nvme_getdev_by_nsid(nsid_str, path_tmp, dev_path); +} + +/* + * Check if the specified fid and nsid leads to a valid nvme device + */ +int nvme_is_device(char *fid_str, char *nsid_str) +{ + char path_tmp[NVME_PATH_MAX+1]; + + return !(nvme_getdev(fid_str, nsid_str, path_tmp)); +} -- 2.21.1