|
20 | 20 | #include <dirent.h> |
21 | 21 | #include <inttypes.h> |
22 | 22 |
|
| 23 | +#include <sys/param.h> |
23 | 24 | #include <sys/stat.h> |
24 | 25 | #include <sys/types.h> |
25 | 26 | #include <arpa/inet.h> |
@@ -980,3 +981,299 @@ char *nvmf_hostid_from_file() |
980 | 981 | { |
981 | 982 | return nvmf_read_file(nvmf_hostid_file, NVMF_HOSTID_SIZE); |
982 | 983 | } |
| 984 | + |
| 985 | +/** |
| 986 | + * nvmf_get_tel() - Calculate the amount of memory needed for a DIE. |
| 987 | + * @hostsymname: Symbolic name (may be NULL) |
| 988 | + * |
| 989 | + * Each Discovery Information Entry (DIE) must contain at a minimum an |
| 990 | + * Extended Attribute for the HostID. The Entry may optionally contain an |
| 991 | + * Extended Attribute for the Symbolic Name. |
| 992 | + * |
| 993 | + * Return: Total Entry Length |
| 994 | + */ |
| 995 | +static __u32 nvmf_get_tel(const char *hostsymname) |
| 996 | +{ |
| 997 | + __u32 tel = sizeof(struct nvmf_ext_die); |
| 998 | + __u16 len; |
| 999 | + |
| 1000 | + /* Host ID is mandatory */ |
| 1001 | + tel += nvmf_exat_size(sizeof(uuid_t)); |
| 1002 | + |
| 1003 | + /* Symbolic name is optional */ |
| 1004 | + len = hostsymname ? strlen(hostsymname) : 0; |
| 1005 | + if (len) |
| 1006 | + tel += nvmf_exat_size(len); |
| 1007 | + |
| 1008 | + return tel; |
| 1009 | +} |
| 1010 | + |
| 1011 | +/** |
| 1012 | + * nvmf_fill_die() - Fill a Discovery Information Entry. |
| 1013 | + * @die: Pointer to Discovery Information Entry to be filled |
| 1014 | + * @h: Pointer to the host data structure |
| 1015 | + * @tel: Length of the DIE |
| 1016 | + * @trtype: Transport type |
| 1017 | + * @adrfam: Address family |
| 1018 | + * @reg_addr: Address to register. Setting this to an empty string tells |
| 1019 | + * the DC to infer address from the source address of the socket. |
| 1020 | + * @tsas: Transport Specific Address Subtype for the address being |
| 1021 | + * registered. |
| 1022 | + */ |
| 1023 | +static void nvmf_fill_die(struct nvmf_ext_die *die, struct nvme_host *h, |
| 1024 | + __u32 tel, __u8 trtype, __u8 adrfam, |
| 1025 | + const char *reg_addr, union nvmf_tsas *tsas) |
| 1026 | +{ |
| 1027 | + __u16 numexat = 0; |
| 1028 | + size_t symname_len; |
| 1029 | + struct nvmf_ext_attr *exat; |
| 1030 | + |
| 1031 | + die->tel = cpu_to_le32(tel); |
| 1032 | + die->trtype = trtype; |
| 1033 | + die->adrfam = adrfam; |
| 1034 | + |
| 1035 | + memcpy(die->nqn, h->hostnqn, MIN(sizeof(die->nqn), strlen(h->hostnqn))); |
| 1036 | + memcpy(die->traddr, reg_addr, MIN(sizeof(die->traddr), strlen(reg_addr))); |
| 1037 | + |
| 1038 | + if (tsas) |
| 1039 | + memcpy(&die->tsas, tsas, sizeof(die->tsas)); |
| 1040 | + |
| 1041 | + /* Extended Attribute for the HostID (mandatory) */ |
| 1042 | + numexat++; |
| 1043 | + exat = die->exat; |
| 1044 | + exat->exattype = cpu_to_le16(NVMF_EXATTYPE_HOSTID); |
| 1045 | + exat->exatlen = cpu_to_le16(nvmf_exat_len(sizeof(uuid_t))); |
| 1046 | + uuid_parse(h->hostid, exat->exatval); |
| 1047 | + |
| 1048 | + /* Extended Attribute for the Symbolic Name (optional) */ |
| 1049 | + symname_len = h->hostsymname ? strlen(h->hostsymname) : 0; |
| 1050 | + if (symname_len) { |
| 1051 | + __u16 exatlen = nvmf_exat_len(symname_len); |
| 1052 | + |
| 1053 | + numexat++; |
| 1054 | + exat = nvmf_exat_ptr_next(exat); |
| 1055 | + exat->exattype = cpu_to_le16(NVMF_EXATTYPE_SYMNAME); |
| 1056 | + exat->exatlen = cpu_to_le16(exatlen); |
| 1057 | + memcpy(exat->exatval, h->hostsymname, symname_len); |
| 1058 | + /* Per Base specs, ASCII strings must be padded with spaces */ |
| 1059 | + memset(&exat->exatval[symname_len], ' ', exatlen - symname_len); |
| 1060 | + } |
| 1061 | + |
| 1062 | + die->numexat = cpu_to_le16(numexat); |
| 1063 | +} |
| 1064 | + |
| 1065 | +/** |
| 1066 | + * nvmf_dim() - Explicit reg, dereg, reg-update issuing DIM |
| 1067 | + * @c: Host NVMe controller instance maintaining the admin queue used to |
| 1068 | + * submit the DIM command to the DC. |
| 1069 | + * @tas: Task field of the Command Dword 10 (cdw10). Indicates whether to |
| 1070 | + * perform a Registration, Deregistration, or Registration-update. |
| 1071 | + * @trtype: Transport type (&enum nvmf_trtype - must be NVMF_TRTYPE_TCP) |
| 1072 | + * @adrfam: Address family (&enum nvmf_addr_family) |
| 1073 | + * @reg_addr: Address to register. Setting this to an empty string tells |
| 1074 | + * the DC to infer address from the source address of the socket. |
| 1075 | + * @tsas: Transport Specific Address Subtype for the address being |
| 1076 | + * registered. |
| 1077 | + * @result: Location where to save the command-specific result returned by |
| 1078 | + * the discovery controller. |
| 1079 | + * |
| 1080 | + * Perform explicit registration, deregistration, or |
| 1081 | + * registration-update (specified by @tas) by sending a Discovery |
| 1082 | + * Information Management (DIM) command to the Discovery Controller |
| 1083 | + * (DC). |
| 1084 | + * |
| 1085 | + * Return: 0 on success; on failure -1 is returned and errno is set |
| 1086 | + */ |
| 1087 | +static int nvmf_dim(nvme_ctrl_t c, enum nvmf_dim_tas tas, __u8 trtype, |
| 1088 | + __u8 adrfam, const char *reg_addr, union nvmf_tsas *tsas, |
| 1089 | + __u32 *result) |
| 1090 | +{ |
| 1091 | + nvme_root_t r = c->s && c->s->h ? c->s->h->r : NULL; |
| 1092 | + struct nvmf_dim_data *dim; |
| 1093 | + struct nvmf_ext_die *die; |
| 1094 | + __u32 tdl; |
| 1095 | + __u32 tel; |
| 1096 | + int ret; |
| 1097 | + |
| 1098 | + struct nvme_dim_args args = { |
| 1099 | + .args_size = sizeof(args), |
| 1100 | + .fd = nvme_ctrl_get_fd(c), |
| 1101 | + .result = result, |
| 1102 | + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, |
| 1103 | + .tas = tas |
| 1104 | + }; |
| 1105 | + |
| 1106 | + if (!c->s) { |
| 1107 | + nvme_msg(r, LOG_ERR, |
| 1108 | + "%s: failed to perform DIM. subsystem undefined.\n", |
| 1109 | + c->name); |
| 1110 | + errno = EINVAL; |
| 1111 | + return -1; |
| 1112 | + } |
| 1113 | + |
| 1114 | + if (!c->s->h) { |
| 1115 | + nvme_msg(r, LOG_ERR, |
| 1116 | + "%s: failed to perform DIM. host undefined.\n", |
| 1117 | + c->name); |
| 1118 | + errno = EINVAL; |
| 1119 | + return -1; |
| 1120 | + } |
| 1121 | + |
| 1122 | + if (!c->s->h->hostid) { |
| 1123 | + nvme_msg(r, LOG_ERR, |
| 1124 | + "%s: failed to perform DIM. hostid undefined.\n", |
| 1125 | + c->name); |
| 1126 | + errno = EINVAL; |
| 1127 | + return -1; |
| 1128 | + } |
| 1129 | + |
| 1130 | + if (!c->s->h->hostnqn) { |
| 1131 | + nvme_msg(r, LOG_ERR, |
| 1132 | + "%s: failed to perform DIM. hostnqn undefined.\n", |
| 1133 | + c->name); |
| 1134 | + errno = EINVAL; |
| 1135 | + return -1; |
| 1136 | + } |
| 1137 | + |
| 1138 | + if (strcmp(c->transport, "tcp")) { |
| 1139 | + nvme_msg(r, LOG_ERR, |
| 1140 | + "%s: DIM only supported for TCP connections.\n", |
| 1141 | + c->name); |
| 1142 | + errno = EINVAL; |
| 1143 | + return -1; |
| 1144 | + } |
| 1145 | + |
| 1146 | + /* Register one Discovery Information Entry (DIE) of size TEL */ |
| 1147 | + tel = nvmf_get_tel(c->s->h->hostsymname); |
| 1148 | + tdl = sizeof(struct nvmf_dim_data) + tel; |
| 1149 | + |
| 1150 | + dim = (struct nvmf_dim_data *)calloc(1, tdl); |
| 1151 | + if (!dim) { |
| 1152 | + errno = ENOMEM; |
| 1153 | + return -1; |
| 1154 | + } |
| 1155 | + |
| 1156 | + dim->tdl = cpu_to_le32(tdl); |
| 1157 | + dim->nument = cpu_to_le64(1); /* only one DIE to register */ |
| 1158 | + dim->entfmt = cpu_to_le16(NVMF_DIM_ENTFMT_EXTENDED); |
| 1159 | + dim->etype = cpu_to_le16(NVMF_DIM_ETYPE_HOST); |
| 1160 | + dim->ektype = cpu_to_le16(0x5F); /* must be 0x5F per specs */ |
| 1161 | + |
| 1162 | + memcpy(dim->eid, c->s->h->hostnqn, |
| 1163 | + MIN(sizeof(dim->eid), strlen(c->s->h->hostnqn))); |
| 1164 | + |
| 1165 | + ret = get_entity_name(dim->ename, sizeof(dim->ename)); |
| 1166 | + if (ret <= 0) |
| 1167 | + nvme_msg(r, LOG_INFO, "%s: Failed to retrieve ENAME. %s.\n", |
| 1168 | + c->name, strerror(errno)); |
| 1169 | + |
| 1170 | + ret = get_entity_version(dim->ever, sizeof(dim->ever)); |
| 1171 | + if (ret <= 0) |
| 1172 | + nvme_msg(r, LOG_INFO, "%s: Failed to retrieve EVER.\n", c->name); |
| 1173 | + |
| 1174 | + die = &dim->die->extended; |
| 1175 | + nvmf_fill_die(die, c->s->h, tel, trtype, adrfam, reg_addr, tsas); |
| 1176 | + |
| 1177 | + args.data_len = tdl; |
| 1178 | + args.data = dim; |
| 1179 | + ret = nvme_dim_send(&args); |
| 1180 | + |
| 1181 | + free(dim); |
| 1182 | + |
| 1183 | + return ret; |
| 1184 | +} |
| 1185 | + |
| 1186 | +/** |
| 1187 | + * nvme_get_adrfam() - Get address family for the address we're registering |
| 1188 | + * with the DC. |
| 1189 | + * |
| 1190 | + * We retrieve this info from the socket itself. If we can't get the source |
| 1191 | + * address from the socket, then we'll infer the address family from the |
| 1192 | + * address of the DC since the DC address has the same address family. |
| 1193 | + * |
| 1194 | + * @ctrl: Host NVMe controller instance maintaining the admin queue used to |
| 1195 | + * submit the DIM command to the DC. |
| 1196 | + * |
| 1197 | + * Return: The address family of the source address associated with the |
| 1198 | + * socket connected to the DC. |
| 1199 | + */ |
| 1200 | +static __u8 nvme_get_adrfam(nvme_ctrl_t c) |
| 1201 | +{ |
| 1202 | + struct sockaddr_storage addr; |
| 1203 | + __u8 adrfam = NVMF_ADDR_FAMILY_IP4; |
| 1204 | + nvme_root_t r = c->s && c->s->h ? c->s->h->r : NULL; |
| 1205 | + |
| 1206 | + if (!inet_pton_with_scope(r, AF_UNSPEC, c->traddr, c->trsvcid, &addr)) { |
| 1207 | + if (addr.ss_family == AF_INET6) |
| 1208 | + adrfam = NVMF_ADDR_FAMILY_IP6; |
| 1209 | + } |
| 1210 | + |
| 1211 | + return adrfam; |
| 1212 | +} |
| 1213 | + |
| 1214 | +/* These string definitions must match with the kernel */ |
| 1215 | +static const char *cntrltype_str[] = { |
| 1216 | + [NVME_CTRL_CNTRLTYPE_IO] = "io", |
| 1217 | + [NVME_CTRL_CNTRLTYPE_DISCOVERY] = "discovery", |
| 1218 | + [NVME_CTRL_CNTRLTYPE_ADMIN] = "admin", |
| 1219 | +}; |
| 1220 | + |
| 1221 | +static const char *dctype_str[] = { |
| 1222 | + [NVME_CTRL_DCTYPE_NOT_REPORTED] = "none", |
| 1223 | + [NVME_CTRL_DCTYPE_DDC] = "ddc", |
| 1224 | + [NVME_CTRL_DCTYPE_CDC] = "cdc", |
| 1225 | +}; |
| 1226 | + |
| 1227 | +/** |
| 1228 | + * nvme_fetch_cntrltype_dctype_from_id - Get cntrltype and dctype from identify command |
| 1229 | + * @c: Controller instance |
| 1230 | + * |
| 1231 | + * On legacy kernels the cntrltype and dctype are not exposed through the |
| 1232 | + * sysfs. We must get them directly from the controller by performing an |
| 1233 | + * identify command. |
| 1234 | + */ |
| 1235 | +static void nvme_fetch_cntrltype_dctype_from_id(nvme_ctrl_t c) |
| 1236 | +{ |
| 1237 | + struct nvme_id_ctrl id = { 0 }; |
| 1238 | + |
| 1239 | + if (nvme_ctrl_identify(c, &id)) |
| 1240 | + return; |
| 1241 | + |
| 1242 | + if (!c->cntrltype) { |
| 1243 | + if (id.cntrltype > NVME_CTRL_CNTRLTYPE_ADMIN || !cntrltype_str[id.cntrltype]) |
| 1244 | + c->cntrltype = strdup("reserved"); |
| 1245 | + else |
| 1246 | + c->cntrltype = strdup(cntrltype_str[id.cntrltype]); |
| 1247 | + } |
| 1248 | + |
| 1249 | + if (!c->dctype) { |
| 1250 | + if (id.dctype > NVME_CTRL_DCTYPE_CDC || !dctype_str[id.dctype]) |
| 1251 | + c->dctype = strdup("reserved"); |
| 1252 | + else |
| 1253 | + c->dctype = strdup(dctype_str[id.dctype]); |
| 1254 | + } |
| 1255 | +} |
| 1256 | + |
| 1257 | +bool nvmf_is_registration_supported(nvme_ctrl_t c) |
| 1258 | +{ |
| 1259 | + if (!c->cntrltype || !c->dctype) |
| 1260 | + nvme_fetch_cntrltype_dctype_from_id(c); |
| 1261 | + |
| 1262 | + return !strcmp(c->dctype, "ddc") || !strcmp(c->dctype, "cdc"); |
| 1263 | +} |
| 1264 | + |
| 1265 | +int nvmf_register_ctrl(nvme_ctrl_t c, enum nvmf_dim_tas tas, __u32 *result) |
| 1266 | +{ |
| 1267 | + if (!nvmf_is_registration_supported(c)) { |
| 1268 | + errno = ENOTSUP; |
| 1269 | + return -1; |
| 1270 | + } |
| 1271 | + |
| 1272 | + /* We're registering our source address with the DC. To do |
| 1273 | + * that, we can simply send an empty string. This tells the DC |
| 1274 | + * to retrieve the source address from the socket and use that |
| 1275 | + * as the registration address. |
| 1276 | + */ |
| 1277 | + return nvmf_dim(c, NVMF_DIM_TAS_REGISTER, NVMF_TRTYPE_TCP, |
| 1278 | + nvme_get_adrfam(c), "", NULL, result); |
| 1279 | +} |
0 commit comments