aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKeith Busch <keith.busch@intel.com>2019-04-09 12:03:59 -0400
committerChristoph Hellwig <hch@lst.de>2019-04-11 11:28:30 -0400
commitd808b7f759b50acf0784ce6230ffa63e12ef465d (patch)
tree6e5d46c670e1ed35f4cddadfec3f7b8c563d23cb
parent67f471b6ed3b09033c4ac77ea03f92afdb1989fe (diff)
nvmet: fix discover log page when offsets are used
The nvme target hadn't been taking the Get Log Page offset parameter into consideration, and so has been returning corrupted log pages when offsets are used. Since many tools, including nvme-cli, split the log request to 4k, we've been breaking discovery log responses when more than 3 subsystems exist. Fix the returned data by internally generating the entire discovery log page and copying only the requested bytes into the user buffer. The command log page offset type has been modified to a native __le64 to make it easier to extract the value from a command. Signed-off-by: Keith Busch <keith.busch@intel.com> Tested-by: Minwoo Im <minwoo.im@samsung.com> Reviewed-by: Chaitanya Kulkarni <chaitanya.kulkarni@wdc.com> Reviewed-by: Hannes Reinecke <hare@suse.com> Reviewed-by: James Smart <james.smart@broadcom.com> Signed-off-by: Christoph Hellwig <hch@lst.de>
-rw-r--r--drivers/nvme/target/admin-cmd.c5
-rw-r--r--drivers/nvme/target/discovery.c68
-rw-r--r--drivers/nvme/target/nvmet.h1
-rw-r--r--include/linux/nvme.h9
4 files changed, 58 insertions, 25 deletions
diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
index 76250181fee0..9f72d515fc4b 100644
--- a/drivers/nvme/target/admin-cmd.c
+++ b/drivers/nvme/target/admin-cmd.c
@@ -24,6 +24,11 @@ u32 nvmet_get_log_page_len(struct nvme_command *cmd)
24 return len; 24 return len;
25} 25}
26 26
27u64 nvmet_get_log_page_offset(struct nvme_command *cmd)
28{
29 return le64_to_cpu(cmd->get_log_page.lpo);
30}
31
27static void nvmet_execute_get_log_page_noop(struct nvmet_req *req) 32static void nvmet_execute_get_log_page_noop(struct nvmet_req *req)
28{ 33{
29 nvmet_req_complete(req, nvmet_zero_sgl(req, 0, req->data_len)); 34 nvmet_req_complete(req, nvmet_zero_sgl(req, 0, req->data_len));
diff --git a/drivers/nvme/target/discovery.c b/drivers/nvme/target/discovery.c
index c872b47a88f3..33ed95e72d6b 100644
--- a/drivers/nvme/target/discovery.c
+++ b/drivers/nvme/target/discovery.c
@@ -131,54 +131,76 @@ static void nvmet_set_disc_traddr(struct nvmet_req *req, struct nvmet_port *port
131 memcpy(traddr, port->disc_addr.traddr, NVMF_TRADDR_SIZE); 131 memcpy(traddr, port->disc_addr.traddr, NVMF_TRADDR_SIZE);
132} 132}
133 133
134static size_t discovery_log_entries(struct nvmet_req *req)
135{
136 struct nvmet_ctrl *ctrl = req->sq->ctrl;
137 struct nvmet_subsys_link *p;
138 struct nvmet_port *r;
139 size_t entries = 0;
140
141 list_for_each_entry(p, &req->port->subsystems, entry) {
142 if (!nvmet_host_allowed(p->subsys, ctrl->hostnqn))
143 continue;
144 entries++;
145 }
146 list_for_each_entry(r, &req->port->referrals, entry)
147 entries++;
148 return entries;
149}
150
134static void nvmet_execute_get_disc_log_page(struct nvmet_req *req) 151static void nvmet_execute_get_disc_log_page(struct nvmet_req *req)
135{ 152{
136 const int entry_size = sizeof(struct nvmf_disc_rsp_page_entry); 153 const int entry_size = sizeof(struct nvmf_disc_rsp_page_entry);
137 struct nvmet_ctrl *ctrl = req->sq->ctrl; 154 struct nvmet_ctrl *ctrl = req->sq->ctrl;
138 struct nvmf_disc_rsp_page_hdr *hdr; 155 struct nvmf_disc_rsp_page_hdr *hdr;
156 u64 offset = nvmet_get_log_page_offset(req->cmd);
139 size_t data_len = nvmet_get_log_page_len(req->cmd); 157 size_t data_len = nvmet_get_log_page_len(req->cmd);
140 size_t alloc_len = max(data_len, sizeof(*hdr)); 158 size_t alloc_len;
141 int residual_len = data_len - sizeof(*hdr);
142 struct nvmet_subsys_link *p; 159 struct nvmet_subsys_link *p;
143 struct nvmet_port *r; 160 struct nvmet_port *r;
144 u32 numrec = 0; 161 u32 numrec = 0;
145 u16 status = 0; 162 u16 status = 0;
163 void *buffer;
164
165 /* Spec requires dword aligned offsets */
166 if (offset & 0x3) {
167 status = NVME_SC_INVALID_FIELD | NVME_SC_DNR;
168 goto out;
169 }
146 170
147 /* 171 /*
148 * Make sure we're passing at least a buffer of response header size. 172 * Make sure we're passing at least a buffer of response header size.
149 * If host provided data len is less than the header size, only the 173 * If host provided data len is less than the header size, only the
150 * number of bytes requested by host will be sent to host. 174 * number of bytes requested by host will be sent to host.
151 */ 175 */
152 hdr = kzalloc(alloc_len, GFP_KERNEL); 176 down_read(&nvmet_config_sem);
153 if (!hdr) { 177 alloc_len = sizeof(*hdr) + entry_size * discovery_log_entries(req);
178 buffer = kzalloc(alloc_len, GFP_KERNEL);
179 if (!buffer) {
180 up_read(&nvmet_config_sem);
154 status = NVME_SC_INTERNAL; 181 status = NVME_SC_INTERNAL;
155 goto out; 182 goto out;
156 } 183 }
157 184
158 down_read(&nvmet_config_sem); 185 hdr = buffer;
159 list_for_each_entry(p, &req->port->subsystems, entry) { 186 list_for_each_entry(p, &req->port->subsystems, entry) {
187 char traddr[NVMF_TRADDR_SIZE];
188
160 if (!nvmet_host_allowed(p->subsys, ctrl->hostnqn)) 189 if (!nvmet_host_allowed(p->subsys, ctrl->hostnqn))
161 continue; 190 continue;
162 if (residual_len >= entry_size) { 191
163 char traddr[NVMF_TRADDR_SIZE]; 192 nvmet_set_disc_traddr(req, req->port, traddr);
164 193 nvmet_format_discovery_entry(hdr, req->port,
165 nvmet_set_disc_traddr(req, req->port, traddr); 194 p->subsys->subsysnqn, traddr,
166 nvmet_format_discovery_entry(hdr, req->port, 195 NVME_NQN_NVME, numrec);
167 p->subsys->subsysnqn, traddr,
168 NVME_NQN_NVME, numrec);
169 residual_len -= entry_size;
170 }
171 numrec++; 196 numrec++;
172 } 197 }
173 198
174 list_for_each_entry(r, &req->port->referrals, entry) { 199 list_for_each_entry(r, &req->port->referrals, entry) {
175 if (residual_len >= entry_size) { 200 nvmet_format_discovery_entry(hdr, r,
176 nvmet_format_discovery_entry(hdr, r, 201 NVME_DISC_SUBSYS_NAME,
177 NVME_DISC_SUBSYS_NAME, 202 r->disc_addr.traddr,
178 r->disc_addr.traddr, 203 NVME_NQN_DISC, numrec);
179 NVME_NQN_DISC, numrec);
180 residual_len -= entry_size;
181 }
182 numrec++; 204 numrec++;
183 } 205 }
184 206
@@ -190,8 +212,8 @@ static void nvmet_execute_get_disc_log_page(struct nvmet_req *req)
190 212
191 up_read(&nvmet_config_sem); 213 up_read(&nvmet_config_sem);
192 214
193 status = nvmet_copy_to_sgl(req, 0, hdr, data_len); 215 status = nvmet_copy_to_sgl(req, 0, buffer + offset, data_len);
194 kfree(hdr); 216 kfree(buffer);
195out: 217out:
196 nvmet_req_complete(req, status); 218 nvmet_req_complete(req, status);
197} 219}
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 51e49efd7849..1653d19b187f 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -428,6 +428,7 @@ u16 nvmet_copy_from_sgl(struct nvmet_req *req, off_t off, void *buf,
428u16 nvmet_zero_sgl(struct nvmet_req *req, off_t off, size_t len); 428u16 nvmet_zero_sgl(struct nvmet_req *req, off_t off, size_t len);
429 429
430u32 nvmet_get_log_page_len(struct nvme_command *cmd); 430u32 nvmet_get_log_page_len(struct nvme_command *cmd);
431u64 nvmet_get_log_page_offset(struct nvme_command *cmd);
431 432
432extern struct list_head *nvmet_ports; 433extern struct list_head *nvmet_ports;
433void nvmet_port_disc_changed(struct nvmet_port *port, 434void nvmet_port_disc_changed(struct nvmet_port *port,
diff --git a/include/linux/nvme.h b/include/linux/nvme.h
index baa49e6a23cc..c40720cb59ac 100644
--- a/include/linux/nvme.h
+++ b/include/linux/nvme.h
@@ -967,8 +967,13 @@ struct nvme_get_log_page_command {
967 __le16 numdl; 967 __le16 numdl;
968 __le16 numdu; 968 __le16 numdu;
969 __u16 rsvd11; 969 __u16 rsvd11;
970 __le32 lpol; 970 union {
971 __le32 lpou; 971 struct {
972 __le32 lpol;
973 __le32 lpou;
974 };
975 __le64 lpo;
976 };
972 __u32 rsvd14[2]; 977 __u32 rsvd14[2];
973}; 978};
974 979