.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
---|
1 | 2 | /* |
---|
2 | 3 | * Discovery service for the NVMe over Fabrics target. |
---|
3 | 4 | * Copyright (C) 2016 Intel Corporation. All rights reserved. |
---|
4 | | - * |
---|
5 | | - * This program is free software; you can redistribute it and/or |
---|
6 | | - * modify it under the terms of the GNU General Public License version |
---|
7 | | - * 2 as published by the Free Software Foundation. |
---|
8 | | - * |
---|
9 | | - * This program is distributed in the hope that it will be useful, |
---|
10 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
11 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
12 | | - * GNU General Public License for more details. |
---|
13 | 5 | */ |
---|
14 | 6 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
---|
15 | 7 | #include <linux/slab.h> |
---|
.. | .. |
---|
18 | 10 | |
---|
19 | 11 | struct nvmet_subsys *nvmet_disc_subsys; |
---|
20 | 12 | |
---|
21 | | -u64 nvmet_genctr; |
---|
| 13 | +static u64 nvmet_genctr; |
---|
| 14 | + |
---|
| 15 | +static void __nvmet_disc_changed(struct nvmet_port *port, |
---|
| 16 | + struct nvmet_ctrl *ctrl) |
---|
| 17 | +{ |
---|
| 18 | + if (ctrl->port != port) |
---|
| 19 | + return; |
---|
| 20 | + |
---|
| 21 | + if (nvmet_aen_bit_disabled(ctrl, NVME_AEN_BIT_DISC_CHANGE)) |
---|
| 22 | + return; |
---|
| 23 | + |
---|
| 24 | + nvmet_add_async_event(ctrl, NVME_AER_TYPE_NOTICE, |
---|
| 25 | + NVME_AER_NOTICE_DISC_CHANGED, NVME_LOG_DISC); |
---|
| 26 | +} |
---|
| 27 | + |
---|
| 28 | +void nvmet_port_disc_changed(struct nvmet_port *port, |
---|
| 29 | + struct nvmet_subsys *subsys) |
---|
| 30 | +{ |
---|
| 31 | + struct nvmet_ctrl *ctrl; |
---|
| 32 | + |
---|
| 33 | + lockdep_assert_held(&nvmet_config_sem); |
---|
| 34 | + nvmet_genctr++; |
---|
| 35 | + |
---|
| 36 | + mutex_lock(&nvmet_disc_subsys->lock); |
---|
| 37 | + list_for_each_entry(ctrl, &nvmet_disc_subsys->ctrls, subsys_entry) { |
---|
| 38 | + if (subsys && !nvmet_host_allowed(subsys, ctrl->hostnqn)) |
---|
| 39 | + continue; |
---|
| 40 | + |
---|
| 41 | + __nvmet_disc_changed(port, ctrl); |
---|
| 42 | + } |
---|
| 43 | + mutex_unlock(&nvmet_disc_subsys->lock); |
---|
| 44 | + |
---|
| 45 | + /* If transport can signal change, notify transport */ |
---|
| 46 | + if (port->tr_ops && port->tr_ops->discovery_chg) |
---|
| 47 | + port->tr_ops->discovery_chg(port); |
---|
| 48 | +} |
---|
| 49 | + |
---|
| 50 | +static void __nvmet_subsys_disc_changed(struct nvmet_port *port, |
---|
| 51 | + struct nvmet_subsys *subsys, |
---|
| 52 | + struct nvmet_host *host) |
---|
| 53 | +{ |
---|
| 54 | + struct nvmet_ctrl *ctrl; |
---|
| 55 | + |
---|
| 56 | + mutex_lock(&nvmet_disc_subsys->lock); |
---|
| 57 | + list_for_each_entry(ctrl, &nvmet_disc_subsys->ctrls, subsys_entry) { |
---|
| 58 | + if (host && strcmp(nvmet_host_name(host), ctrl->hostnqn)) |
---|
| 59 | + continue; |
---|
| 60 | + |
---|
| 61 | + __nvmet_disc_changed(port, ctrl); |
---|
| 62 | + } |
---|
| 63 | + mutex_unlock(&nvmet_disc_subsys->lock); |
---|
| 64 | +} |
---|
| 65 | + |
---|
| 66 | +void nvmet_subsys_disc_changed(struct nvmet_subsys *subsys, |
---|
| 67 | + struct nvmet_host *host) |
---|
| 68 | +{ |
---|
| 69 | + struct nvmet_port *port; |
---|
| 70 | + struct nvmet_subsys_link *s; |
---|
| 71 | + |
---|
| 72 | + nvmet_genctr++; |
---|
| 73 | + |
---|
| 74 | + list_for_each_entry(port, nvmet_ports, global_entry) |
---|
| 75 | + list_for_each_entry(s, &port->subsystems, entry) { |
---|
| 76 | + if (s->subsys != subsys) |
---|
| 77 | + continue; |
---|
| 78 | + __nvmet_subsys_disc_changed(port, subsys, host); |
---|
| 79 | + } |
---|
| 80 | +} |
---|
22 | 81 | |
---|
23 | 82 | void nvmet_referral_enable(struct nvmet_port *parent, struct nvmet_port *port) |
---|
24 | 83 | { |
---|
.. | .. |
---|
26 | 85 | if (list_empty(&port->entry)) { |
---|
27 | 86 | list_add_tail(&port->entry, &parent->referrals); |
---|
28 | 87 | port->enabled = true; |
---|
29 | | - nvmet_genctr++; |
---|
| 88 | + nvmet_port_disc_changed(parent, NULL); |
---|
30 | 89 | } |
---|
31 | 90 | up_write(&nvmet_config_sem); |
---|
32 | 91 | } |
---|
33 | 92 | |
---|
34 | | -void nvmet_referral_disable(struct nvmet_port *port) |
---|
| 93 | +void nvmet_referral_disable(struct nvmet_port *parent, struct nvmet_port *port) |
---|
35 | 94 | { |
---|
36 | 95 | down_write(&nvmet_config_sem); |
---|
37 | 96 | if (!list_empty(&port->entry)) { |
---|
38 | 97 | port->enabled = false; |
---|
39 | 98 | list_del_init(&port->entry); |
---|
40 | | - nvmet_genctr++; |
---|
| 99 | + nvmet_port_disc_changed(parent, NULL); |
---|
41 | 100 | } |
---|
42 | 101 | up_write(&nvmet_config_sem); |
---|
43 | 102 | } |
---|
.. | .. |
---|
81 | 140 | memcpy(traddr, port->disc_addr.traddr, NVMF_TRADDR_SIZE); |
---|
82 | 141 | } |
---|
83 | 142 | |
---|
84 | | -static void nvmet_execute_get_disc_log_page(struct nvmet_req *req) |
---|
| 143 | +static size_t discovery_log_entries(struct nvmet_req *req) |
---|
| 144 | +{ |
---|
| 145 | + struct nvmet_ctrl *ctrl = req->sq->ctrl; |
---|
| 146 | + struct nvmet_subsys_link *p; |
---|
| 147 | + struct nvmet_port *r; |
---|
| 148 | + size_t entries = 0; |
---|
| 149 | + |
---|
| 150 | + list_for_each_entry(p, &req->port->subsystems, entry) { |
---|
| 151 | + if (!nvmet_host_allowed(p->subsys, ctrl->hostnqn)) |
---|
| 152 | + continue; |
---|
| 153 | + entries++; |
---|
| 154 | + } |
---|
| 155 | + list_for_each_entry(r, &req->port->referrals, entry) |
---|
| 156 | + entries++; |
---|
| 157 | + return entries; |
---|
| 158 | +} |
---|
| 159 | + |
---|
| 160 | +static void nvmet_execute_disc_get_log_page(struct nvmet_req *req) |
---|
85 | 161 | { |
---|
86 | 162 | const int entry_size = sizeof(struct nvmf_disc_rsp_page_entry); |
---|
87 | 163 | struct nvmet_ctrl *ctrl = req->sq->ctrl; |
---|
88 | 164 | struct nvmf_disc_rsp_page_hdr *hdr; |
---|
| 165 | + u64 offset = nvmet_get_log_page_offset(req->cmd); |
---|
89 | 166 | size_t data_len = nvmet_get_log_page_len(req->cmd); |
---|
90 | | - size_t alloc_len = max(data_len, sizeof(*hdr)); |
---|
91 | | - int residual_len = data_len - sizeof(*hdr); |
---|
| 167 | + size_t alloc_len; |
---|
92 | 168 | struct nvmet_subsys_link *p; |
---|
93 | 169 | struct nvmet_port *r; |
---|
94 | 170 | u32 numrec = 0; |
---|
95 | 171 | u16 status = 0; |
---|
| 172 | + void *buffer; |
---|
| 173 | + |
---|
| 174 | + if (!nvmet_check_transfer_len(req, data_len)) |
---|
| 175 | + return; |
---|
| 176 | + |
---|
| 177 | + if (req->cmd->get_log_page.lid != NVME_LOG_DISC) { |
---|
| 178 | + req->error_loc = |
---|
| 179 | + offsetof(struct nvme_get_log_page_command, lid); |
---|
| 180 | + status = NVME_SC_INVALID_FIELD | NVME_SC_DNR; |
---|
| 181 | + goto out; |
---|
| 182 | + } |
---|
| 183 | + |
---|
| 184 | + /* Spec requires dword aligned offsets */ |
---|
| 185 | + if (offset & 0x3) { |
---|
| 186 | + req->error_loc = |
---|
| 187 | + offsetof(struct nvme_get_log_page_command, lpo); |
---|
| 188 | + status = NVME_SC_INVALID_FIELD | NVME_SC_DNR; |
---|
| 189 | + goto out; |
---|
| 190 | + } |
---|
96 | 191 | |
---|
97 | 192 | /* |
---|
98 | 193 | * Make sure we're passing at least a buffer of response header size. |
---|
99 | 194 | * If host provided data len is less than the header size, only the |
---|
100 | 195 | * number of bytes requested by host will be sent to host. |
---|
101 | 196 | */ |
---|
102 | | - hdr = kzalloc(alloc_len, GFP_KERNEL); |
---|
103 | | - if (!hdr) { |
---|
| 197 | + down_read(&nvmet_config_sem); |
---|
| 198 | + alloc_len = sizeof(*hdr) + entry_size * discovery_log_entries(req); |
---|
| 199 | + buffer = kzalloc(alloc_len, GFP_KERNEL); |
---|
| 200 | + if (!buffer) { |
---|
| 201 | + up_read(&nvmet_config_sem); |
---|
104 | 202 | status = NVME_SC_INTERNAL; |
---|
105 | 203 | goto out; |
---|
106 | 204 | } |
---|
107 | 205 | |
---|
108 | | - down_read(&nvmet_config_sem); |
---|
| 206 | + hdr = buffer; |
---|
109 | 207 | list_for_each_entry(p, &req->port->subsystems, entry) { |
---|
110 | | - if (!nvmet_host_allowed(req, p->subsys, ctrl->hostnqn)) |
---|
111 | | - continue; |
---|
112 | | - if (residual_len >= entry_size) { |
---|
113 | | - char traddr[NVMF_TRADDR_SIZE]; |
---|
| 208 | + char traddr[NVMF_TRADDR_SIZE]; |
---|
114 | 209 | |
---|
115 | | - nvmet_set_disc_traddr(req, req->port, traddr); |
---|
116 | | - nvmet_format_discovery_entry(hdr, req->port, |
---|
117 | | - p->subsys->subsysnqn, traddr, |
---|
118 | | - NVME_NQN_NVME, numrec); |
---|
119 | | - residual_len -= entry_size; |
---|
120 | | - } |
---|
| 210 | + if (!nvmet_host_allowed(p->subsys, ctrl->hostnqn)) |
---|
| 211 | + continue; |
---|
| 212 | + |
---|
| 213 | + nvmet_set_disc_traddr(req, req->port, traddr); |
---|
| 214 | + nvmet_format_discovery_entry(hdr, req->port, |
---|
| 215 | + p->subsys->subsysnqn, traddr, |
---|
| 216 | + NVME_NQN_NVME, numrec); |
---|
121 | 217 | numrec++; |
---|
122 | 218 | } |
---|
123 | 219 | |
---|
124 | 220 | list_for_each_entry(r, &req->port->referrals, entry) { |
---|
125 | | - if (residual_len >= entry_size) { |
---|
126 | | - nvmet_format_discovery_entry(hdr, r, |
---|
127 | | - NVME_DISC_SUBSYS_NAME, |
---|
128 | | - r->disc_addr.traddr, |
---|
129 | | - NVME_NQN_DISC, numrec); |
---|
130 | | - residual_len -= entry_size; |
---|
131 | | - } |
---|
| 221 | + nvmet_format_discovery_entry(hdr, r, |
---|
| 222 | + NVME_DISC_SUBSYS_NAME, |
---|
| 223 | + r->disc_addr.traddr, |
---|
| 224 | + NVME_NQN_DISC, numrec); |
---|
132 | 225 | numrec++; |
---|
133 | 226 | } |
---|
134 | 227 | |
---|
.. | .. |
---|
136 | 229 | hdr->numrec = cpu_to_le64(numrec); |
---|
137 | 230 | hdr->recfmt = cpu_to_le16(0); |
---|
138 | 231 | |
---|
| 232 | + nvmet_clear_aen_bit(req, NVME_AEN_BIT_DISC_CHANGE); |
---|
| 233 | + |
---|
139 | 234 | up_read(&nvmet_config_sem); |
---|
140 | 235 | |
---|
141 | | - status = nvmet_copy_to_sgl(req, 0, hdr, data_len); |
---|
142 | | - kfree(hdr); |
---|
| 236 | + status = nvmet_copy_to_sgl(req, 0, buffer + offset, data_len); |
---|
| 237 | + kfree(buffer); |
---|
143 | 238 | out: |
---|
144 | 239 | nvmet_req_complete(req, status); |
---|
145 | 240 | } |
---|
146 | 241 | |
---|
147 | | -static void nvmet_execute_identify_disc_ctrl(struct nvmet_req *req) |
---|
| 242 | +static void nvmet_execute_disc_identify(struct nvmet_req *req) |
---|
148 | 243 | { |
---|
149 | 244 | struct nvmet_ctrl *ctrl = req->sq->ctrl; |
---|
150 | 245 | struct nvme_id_ctrl *id; |
---|
| 246 | + const char model[] = "Linux"; |
---|
151 | 247 | u16 status = 0; |
---|
| 248 | + |
---|
| 249 | + if (!nvmet_check_transfer_len(req, NVME_IDENTIFY_DATA_SIZE)) |
---|
| 250 | + return; |
---|
| 251 | + |
---|
| 252 | + if (req->cmd->identify.cns != NVME_ID_CNS_CTRL) { |
---|
| 253 | + req->error_loc = offsetof(struct nvme_identify, cns); |
---|
| 254 | + status = NVME_SC_INVALID_FIELD | NVME_SC_DNR; |
---|
| 255 | + goto out; |
---|
| 256 | + } |
---|
152 | 257 | |
---|
153 | 258 | id = kzalloc(sizeof(*id), GFP_KERNEL); |
---|
154 | 259 | if (!id) { |
---|
.. | .. |
---|
156 | 261 | goto out; |
---|
157 | 262 | } |
---|
158 | 263 | |
---|
| 264 | + memset(id->sn, ' ', sizeof(id->sn)); |
---|
| 265 | + bin2hex(id->sn, &ctrl->subsys->serial, |
---|
| 266 | + min(sizeof(ctrl->subsys->serial), sizeof(id->sn) / 2)); |
---|
159 | 267 | memset(id->fr, ' ', sizeof(id->fr)); |
---|
160 | | - strncpy((char *)id->fr, UTS_RELEASE, sizeof(id->fr)); |
---|
| 268 | + memcpy_and_pad(id->mn, sizeof(id->mn), model, sizeof(model) - 1, ' '); |
---|
| 269 | + memcpy_and_pad(id->fr, sizeof(id->fr), |
---|
| 270 | + UTS_RELEASE, strlen(UTS_RELEASE), ' '); |
---|
161 | 271 | |
---|
162 | 272 | /* no limit on data transfer sizes for now */ |
---|
163 | 273 | id->mdts = 0; |
---|
.. | .. |
---|
169 | 279 | id->maxcmd = cpu_to_le16(NVMET_MAX_CMD); |
---|
170 | 280 | |
---|
171 | 281 | id->sgls = cpu_to_le32(1 << 0); /* we always support SGLs */ |
---|
172 | | - if (ctrl->ops->has_keyed_sgls) |
---|
| 282 | + if (ctrl->ops->flags & NVMF_KEYED_SGLS) |
---|
173 | 283 | id->sgls |= cpu_to_le32(1 << 2); |
---|
174 | 284 | if (req->port->inline_data_size) |
---|
175 | 285 | id->sgls |= cpu_to_le32(1 << 20); |
---|
176 | 286 | |
---|
177 | | - strcpy(id->subnqn, ctrl->subsys->subsysnqn); |
---|
| 287 | + id->oaes = cpu_to_le32(NVMET_DISC_AEN_CFG_OPTIONAL); |
---|
| 288 | + |
---|
| 289 | + strlcpy(id->subnqn, ctrl->subsys->subsysnqn, sizeof(id->subnqn)); |
---|
178 | 290 | |
---|
179 | 291 | status = nvmet_copy_to_sgl(req, 0, id, sizeof(*id)); |
---|
180 | 292 | |
---|
181 | 293 | kfree(id); |
---|
182 | 294 | out: |
---|
183 | 295 | nvmet_req_complete(req, status); |
---|
| 296 | +} |
---|
| 297 | + |
---|
| 298 | +static void nvmet_execute_disc_set_features(struct nvmet_req *req) |
---|
| 299 | +{ |
---|
| 300 | + u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); |
---|
| 301 | + u16 stat; |
---|
| 302 | + |
---|
| 303 | + if (!nvmet_check_transfer_len(req, 0)) |
---|
| 304 | + return; |
---|
| 305 | + |
---|
| 306 | + switch (cdw10 & 0xff) { |
---|
| 307 | + case NVME_FEAT_KATO: |
---|
| 308 | + stat = nvmet_set_feat_kato(req); |
---|
| 309 | + break; |
---|
| 310 | + case NVME_FEAT_ASYNC_EVENT: |
---|
| 311 | + stat = nvmet_set_feat_async_event(req, |
---|
| 312 | + NVMET_DISC_AEN_CFG_OPTIONAL); |
---|
| 313 | + break; |
---|
| 314 | + default: |
---|
| 315 | + req->error_loc = |
---|
| 316 | + offsetof(struct nvme_common_command, cdw10); |
---|
| 317 | + stat = NVME_SC_INVALID_FIELD | NVME_SC_DNR; |
---|
| 318 | + break; |
---|
| 319 | + } |
---|
| 320 | + |
---|
| 321 | + nvmet_req_complete(req, stat); |
---|
| 322 | +} |
---|
| 323 | + |
---|
| 324 | +static void nvmet_execute_disc_get_features(struct nvmet_req *req) |
---|
| 325 | +{ |
---|
| 326 | + u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10); |
---|
| 327 | + u16 stat = 0; |
---|
| 328 | + |
---|
| 329 | + if (!nvmet_check_transfer_len(req, 0)) |
---|
| 330 | + return; |
---|
| 331 | + |
---|
| 332 | + switch (cdw10 & 0xff) { |
---|
| 333 | + case NVME_FEAT_KATO: |
---|
| 334 | + nvmet_get_feat_kato(req); |
---|
| 335 | + break; |
---|
| 336 | + case NVME_FEAT_ASYNC_EVENT: |
---|
| 337 | + nvmet_get_feat_async_event(req); |
---|
| 338 | + break; |
---|
| 339 | + default: |
---|
| 340 | + req->error_loc = |
---|
| 341 | + offsetof(struct nvme_common_command, cdw10); |
---|
| 342 | + stat = NVME_SC_INVALID_FIELD | NVME_SC_DNR; |
---|
| 343 | + break; |
---|
| 344 | + } |
---|
| 345 | + |
---|
| 346 | + nvmet_req_complete(req, stat); |
---|
184 | 347 | } |
---|
185 | 348 | |
---|
186 | 349 | u16 nvmet_parse_discovery_cmd(struct nvmet_req *req) |
---|
.. | .. |
---|
190 | 353 | if (unlikely(!(req->sq->ctrl->csts & NVME_CSTS_RDY))) { |
---|
191 | 354 | pr_err("got cmd %d while not ready\n", |
---|
192 | 355 | cmd->common.opcode); |
---|
| 356 | + req->error_loc = |
---|
| 357 | + offsetof(struct nvme_common_command, opcode); |
---|
193 | 358 | return NVME_SC_INVALID_OPCODE | NVME_SC_DNR; |
---|
194 | 359 | } |
---|
195 | 360 | |
---|
196 | 361 | switch (cmd->common.opcode) { |
---|
| 362 | + case nvme_admin_set_features: |
---|
| 363 | + req->execute = nvmet_execute_disc_set_features; |
---|
| 364 | + return 0; |
---|
| 365 | + case nvme_admin_get_features: |
---|
| 366 | + req->execute = nvmet_execute_disc_get_features; |
---|
| 367 | + return 0; |
---|
| 368 | + case nvme_admin_async_event: |
---|
| 369 | + req->execute = nvmet_execute_async_event; |
---|
| 370 | + return 0; |
---|
| 371 | + case nvme_admin_keep_alive: |
---|
| 372 | + req->execute = nvmet_execute_keep_alive; |
---|
| 373 | + return 0; |
---|
197 | 374 | case nvme_admin_get_log_page: |
---|
198 | | - req->data_len = nvmet_get_log_page_len(cmd); |
---|
199 | | - |
---|
200 | | - switch (cmd->get_log_page.lid) { |
---|
201 | | - case NVME_LOG_DISC: |
---|
202 | | - req->execute = nvmet_execute_get_disc_log_page; |
---|
203 | | - return 0; |
---|
204 | | - default: |
---|
205 | | - pr_err("unsupported get_log_page lid %d\n", |
---|
206 | | - cmd->get_log_page.lid); |
---|
207 | | - return NVME_SC_INVALID_OPCODE | NVME_SC_DNR; |
---|
208 | | - } |
---|
| 375 | + req->execute = nvmet_execute_disc_get_log_page; |
---|
| 376 | + return 0; |
---|
209 | 377 | case nvme_admin_identify: |
---|
210 | | - req->data_len = NVME_IDENTIFY_DATA_SIZE; |
---|
211 | | - switch (cmd->identify.cns) { |
---|
212 | | - case NVME_ID_CNS_CTRL: |
---|
213 | | - req->execute = |
---|
214 | | - nvmet_execute_identify_disc_ctrl; |
---|
215 | | - return 0; |
---|
216 | | - default: |
---|
217 | | - pr_err("unsupported identify cns %d\n", |
---|
218 | | - cmd->identify.cns); |
---|
219 | | - return NVME_SC_INVALID_OPCODE | NVME_SC_DNR; |
---|
220 | | - } |
---|
| 378 | + req->execute = nvmet_execute_disc_identify; |
---|
| 379 | + return 0; |
---|
221 | 380 | default: |
---|
222 | | - pr_err("unsupported cmd %d\n", cmd->common.opcode); |
---|
| 381 | + pr_err("unhandled cmd %d\n", cmd->common.opcode); |
---|
| 382 | + req->error_loc = offsetof(struct nvme_common_command, opcode); |
---|
223 | 383 | return NVME_SC_INVALID_OPCODE | NVME_SC_DNR; |
---|
224 | 384 | } |
---|
225 | 385 | |
---|
226 | | - pr_err("unhandled cmd %d\n", cmd->common.opcode); |
---|
227 | | - return NVME_SC_INVALID_OPCODE | NVME_SC_DNR; |
---|
228 | 386 | } |
---|
229 | 387 | |
---|
230 | 388 | int __init nvmet_init_discovery(void) |
---|
231 | 389 | { |
---|
232 | 390 | nvmet_disc_subsys = |
---|
233 | 391 | nvmet_subsys_alloc(NVME_DISC_SUBSYS_NAME, NVME_NQN_DISC); |
---|
234 | | - if (!nvmet_disc_subsys) |
---|
235 | | - return -ENOMEM; |
---|
236 | | - return 0; |
---|
| 392 | + return PTR_ERR_OR_ZERO(nvmet_disc_subsys); |
---|
237 | 393 | } |
---|
238 | 394 | |
---|
239 | 395 | void nvmet_exit_discovery(void) |
---|