diff --git a/ipl_tools/Makefile b/ipl_tools/Makefile index fe090417..e5ad00eb 100644 --- a/ipl_tools/Makefile +++ b/ipl_tools/Makefile @@ -4,9 +4,7 @@ libs = $(rootdir)/libutil/libutil.a 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) diff --git a/ipl_tools/cmd_chreipl.c b/ipl_tools/cmd_chreipl.c index d083fdba..a296f91a 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" @@ -70,6 +74,12 @@ static const char *const usage_chreipl = " -L, --loadparm Loadparm specification\n" " -c, --clear 0|1 Control if memory is cleared on re-IPL\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" @@ -86,6 +96,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; @@ -94,10 +108,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 */ int reipl_clear; } l; @@ -229,6 +243,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) { /* @@ -248,6 +290,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) { /* @@ -288,6 +352,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 || @@ -295,11 +366,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; } @@ -402,6 +480,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; @@ -423,6 +504,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) @@ -482,6 +571,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' }, @@ -490,7 +581,7 @@ static void parse_chreipl_options(int argc, char *argv[]) { "clear", required_argument, NULL, 'c' }, { NULL, 0, NULL, 0 } }; - static const char optstr[] = "hd:vw:l:fL:b:n:p:c:"; + static const char optstr[] = "hd:vw:l:fL:b:n:p:c:i:s:"; /* dont run without any argument */ if (argc == 1) @@ -502,6 +593,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 @@ -516,9 +609,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; @@ -680,6 +779,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(); @@ -720,6 +853,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"); } @@ -735,6 +875,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 a829b9d3..af9b9bca 100644 --- a/ipl_tools/cmd_lsreipl.c +++ b/ipl_tools/cmd_lsreipl.c @@ -84,6 +84,35 @@ void print_fcp(int show_ipl, int dump) print_fw_str("clear: %s\n", dir, "clear"); } +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]; @@ -155,6 +184,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 50d0b743..576d33a8 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 91ddfad6..e6ffc16c 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. @@ -207,6 +215,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. @@ -221,7 +267,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, 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 @@ -244,6 +290,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 00000000..2a959729 --- /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)); +}