diff options
author | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2007-03-02 19:55:54 -0500 |
---|---|---|
committer | James Bottomley <jejb@mulgrave.il.steeleye.com> | 2007-03-11 12:39:27 -0400 |
commit | bc7e380a6a4c94f79a49c36bdb28062a750b3c2b (patch) | |
tree | c5ae13015fd0f7f15e9dbf16f865a11d0c495f3b | |
parent | 181011e04a2a32f8d5df212254239ac9a3c8ab5e (diff) |
[SCSI] tgt: fix sesnse buffer problems
This patch simplify the way to notify LLDs of the command completion
and addresses the following sense buffer problems:
- can't handle both data and sense.
- forces user-space to use aligned sense buffer
tgt copies sense_data from userspace to cmnd->sense_buffer (if
necessary), maps user-space pages (if necessary) and then calls
host->transfer_response (host->transfer_data is removed).
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Signed-off-by: Mike Christie <michaelc@cs.wisc.edu>
Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
-rw-r--r-- | drivers/scsi/ibmvscsi/ibmvstgt.c | 21 | ||||
-rw-r--r-- | drivers/scsi/scsi_tgt_if.c | 6 | ||||
-rw-r--r-- | drivers/scsi/scsi_tgt_lib.c | 120 | ||||
-rw-r--r-- | drivers/scsi/scsi_tgt_priv.h | 5 | ||||
-rw-r--r-- | include/scsi/scsi_host.h | 19 | ||||
-rw-r--r-- | include/scsi/scsi_tgt_if.h | 6 |
6 files changed, 44 insertions, 133 deletions
diff --git a/drivers/scsi/ibmvscsi/ibmvstgt.c b/drivers/scsi/ibmvscsi/ibmvstgt.c index 4368ca0e8270..a7fbd10817a0 100644 --- a/drivers/scsi/ibmvscsi/ibmvstgt.c +++ b/drivers/scsi/ibmvscsi/ibmvstgt.c | |||
@@ -273,23 +273,9 @@ static int ibmvstgt_rdma(struct scsi_cmnd *sc, struct scatterlist *sg, int nsg, | |||
273 | rest -= mlen; | 273 | rest -= mlen; |
274 | } | 274 | } |
275 | out: | 275 | out: |
276 | |||
277 | return 0; | 276 | return 0; |
278 | } | 277 | } |
279 | 278 | ||
280 | static int ibmvstgt_transfer_data(struct scsi_cmnd *sc, | ||
281 | void (*done)(struct scsi_cmnd *)) | ||
282 | { | ||
283 | struct iu_entry *iue = (struct iu_entry *) sc->SCp.ptr; | ||
284 | int err; | ||
285 | |||
286 | err = srp_transfer_data(sc, &vio_iu(iue)->srp.cmd, ibmvstgt_rdma, 1, 1); | ||
287 | |||
288 | done(sc); | ||
289 | |||
290 | return err; | ||
291 | } | ||
292 | |||
293 | static int ibmvstgt_cmd_done(struct scsi_cmnd *sc, | 279 | static int ibmvstgt_cmd_done(struct scsi_cmnd *sc, |
294 | void (*done)(struct scsi_cmnd *)) | 280 | void (*done)(struct scsi_cmnd *)) |
295 | { | 281 | { |
@@ -297,7 +283,11 @@ static int ibmvstgt_cmd_done(struct scsi_cmnd *sc, | |||
297 | struct iu_entry *iue = (struct iu_entry *) sc->SCp.ptr; | 283 | struct iu_entry *iue = (struct iu_entry *) sc->SCp.ptr; |
298 | struct srp_target *target = iue->target; | 284 | struct srp_target *target = iue->target; |
299 | 285 | ||
300 | dprintk("%p %p %x\n", iue, target, vio_iu(iue)->srp.cmd.cdb[0]); | 286 | dprintk("%p %p %x %u\n", iue, target, vio_iu(iue)->srp.cmd.cdb[0], |
287 | cmd->usg_sg); | ||
288 | |||
289 | if (sc->use_sg) | ||
290 | srp_transfer_data(sc, &vio_iu(iue)->srp.cmd, ibmvstgt_rdma, 1, 1); | ||
301 | 291 | ||
302 | spin_lock_irqsave(&target->lock, flags); | 292 | spin_lock_irqsave(&target->lock, flags); |
303 | list_del(&iue->ilist); | 293 | list_del(&iue->ilist); |
@@ -794,7 +784,6 @@ static struct scsi_host_template ibmvstgt_sht = { | |||
794 | .use_clustering = DISABLE_CLUSTERING, | 784 | .use_clustering = DISABLE_CLUSTERING, |
795 | .max_sectors = DEFAULT_MAX_SECTORS, | 785 | .max_sectors = DEFAULT_MAX_SECTORS, |
796 | .transfer_response = ibmvstgt_cmd_done, | 786 | .transfer_response = ibmvstgt_cmd_done, |
797 | .transfer_data = ibmvstgt_transfer_data, | ||
798 | .eh_abort_handler = ibmvstgt_eh_abort_handler, | 787 | .eh_abort_handler = ibmvstgt_eh_abort_handler, |
799 | .tsk_mgmt_response = ibmvstgt_tsk_mgmt_response, | 788 | .tsk_mgmt_response = ibmvstgt_tsk_mgmt_response, |
800 | .shost_attrs = ibmvstgt_attrs, | 789 | .shost_attrs = ibmvstgt_attrs, |
diff --git a/drivers/scsi/scsi_tgt_if.c b/drivers/scsi/scsi_tgt_if.c index 0e08817fdecf..ca22ddf81746 100644 --- a/drivers/scsi/scsi_tgt_if.c +++ b/drivers/scsi/scsi_tgt_if.c | |||
@@ -179,10 +179,12 @@ static int event_recv_msg(struct tgt_event *ev) | |||
179 | switch (ev->hdr.type) { | 179 | switch (ev->hdr.type) { |
180 | case TGT_UEVENT_CMD_RSP: | 180 | case TGT_UEVENT_CMD_RSP: |
181 | err = scsi_tgt_kspace_exec(ev->p.cmd_rsp.host_no, | 181 | err = scsi_tgt_kspace_exec(ev->p.cmd_rsp.host_no, |
182 | ev->p.cmd_rsp.tag, | ||
183 | ev->p.cmd_rsp.result, | 182 | ev->p.cmd_rsp.result, |
184 | ev->p.cmd_rsp.len, | 183 | ev->p.cmd_rsp.tag, |
185 | ev->p.cmd_rsp.uaddr, | 184 | ev->p.cmd_rsp.uaddr, |
185 | ev->p.cmd_rsp.len, | ||
186 | ev->p.cmd_rsp.sense_uaddr, | ||
187 | ev->p.cmd_rsp.sense_len, | ||
186 | ev->p.cmd_rsp.rw); | 188 | ev->p.cmd_rsp.rw); |
187 | break; | 189 | break; |
188 | case TGT_UEVENT_TSK_MGMT_RSP: | 190 | case TGT_UEVENT_TSK_MGMT_RSP: |
diff --git a/drivers/scsi/scsi_tgt_lib.c b/drivers/scsi/scsi_tgt_lib.c index 47c29a98c922..dc8781a68d7c 100644 --- a/drivers/scsi/scsi_tgt_lib.c +++ b/drivers/scsi/scsi_tgt_lib.c | |||
@@ -47,9 +47,6 @@ struct scsi_tgt_cmd { | |||
47 | struct list_head hash_list; | 47 | struct list_head hash_list; |
48 | struct request *rq; | 48 | struct request *rq; |
49 | u64 tag; | 49 | u64 tag; |
50 | |||
51 | void *buffer; | ||
52 | unsigned bufflen; | ||
53 | }; | 50 | }; |
54 | 51 | ||
55 | #define TGT_HASH_ORDER 4 | 52 | #define TGT_HASH_ORDER 4 |
@@ -330,10 +327,14 @@ static void scsi_tgt_cmd_done(struct scsi_cmnd *cmd) | |||
330 | dprintk("cmd %p %lu\n", cmd, rq_data_dir(cmd->request)); | 327 | dprintk("cmd %p %lu\n", cmd, rq_data_dir(cmd->request)); |
331 | 328 | ||
332 | scsi_tgt_uspace_send_status(cmd, tcmd->tag); | 329 | scsi_tgt_uspace_send_status(cmd, tcmd->tag); |
330 | |||
331 | if (cmd->request_buffer) | ||
332 | scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); | ||
333 | |||
333 | queue_work(scsi_tgtd, &tcmd->work); | 334 | queue_work(scsi_tgtd, &tcmd->work); |
334 | } | 335 | } |
335 | 336 | ||
336 | static int __scsi_tgt_transfer_response(struct scsi_cmnd *cmd) | 337 | static int scsi_tgt_transfer_response(struct scsi_cmnd *cmd) |
337 | { | 338 | { |
338 | struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); | 339 | struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); |
339 | int err; | 340 | int err; |
@@ -346,30 +347,12 @@ static int __scsi_tgt_transfer_response(struct scsi_cmnd *cmd) | |||
346 | case SCSI_MLQUEUE_DEVICE_BUSY: | 347 | case SCSI_MLQUEUE_DEVICE_BUSY: |
347 | return -EAGAIN; | 348 | return -EAGAIN; |
348 | } | 349 | } |
349 | |||
350 | return 0; | 350 | return 0; |
351 | } | 351 | } |
352 | 352 | ||
353 | static void scsi_tgt_transfer_response(struct scsi_cmnd *cmd) | ||
354 | { | ||
355 | struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; | ||
356 | int err; | ||
357 | |||
358 | err = __scsi_tgt_transfer_response(cmd); | ||
359 | if (!err) | ||
360 | return; | ||
361 | |||
362 | cmd->result = DID_BUS_BUSY << 16; | ||
363 | err = scsi_tgt_uspace_send_status(cmd, tcmd->tag); | ||
364 | if (err <= 0) | ||
365 | /* the eh will have to pick this up */ | ||
366 | printk(KERN_ERR "Could not send cmd %p status\n", cmd); | ||
367 | } | ||
368 | |||
369 | static int scsi_tgt_init_cmd(struct scsi_cmnd *cmd, gfp_t gfp_mask) | 353 | static int scsi_tgt_init_cmd(struct scsi_cmnd *cmd, gfp_t gfp_mask) |
370 | { | 354 | { |
371 | struct request *rq = cmd->request; | 355 | struct request *rq = cmd->request; |
372 | struct scsi_tgt_cmd *tcmd = rq->end_io_data; | ||
373 | int count; | 356 | int count; |
374 | 357 | ||
375 | cmd->use_sg = rq->nr_phys_segments; | 358 | cmd->use_sg = rq->nr_phys_segments; |
@@ -379,31 +362,28 @@ static int scsi_tgt_init_cmd(struct scsi_cmnd *cmd, gfp_t gfp_mask) | |||
379 | 362 | ||
380 | cmd->request_bufflen = rq->data_len; | 363 | cmd->request_bufflen = rq->data_len; |
381 | 364 | ||
382 | dprintk("cmd %p addr %p cnt %d %lu\n", cmd, tcmd->buffer, cmd->use_sg, | 365 | dprintk("cmd %p cnt %d %lu\n", cmd, cmd->use_sg, rq_data_dir(rq)); |
383 | rq_data_dir(rq)); | ||
384 | count = blk_rq_map_sg(rq->q, rq, cmd->request_buffer); | 366 | count = blk_rq_map_sg(rq->q, rq, cmd->request_buffer); |
385 | if (likely(count <= cmd->use_sg)) { | 367 | if (likely(count <= cmd->use_sg)) { |
386 | cmd->use_sg = count; | 368 | cmd->use_sg = count; |
387 | return 0; | 369 | return 0; |
388 | } | 370 | } |
389 | 371 | ||
390 | eprintk("cmd %p addr %p cnt %d\n", cmd, tcmd->buffer, cmd->use_sg); | 372 | eprintk("cmd %p cnt %d\n", cmd, cmd->use_sg); |
391 | scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); | 373 | scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); |
392 | return -EINVAL; | 374 | return -EINVAL; |
393 | } | 375 | } |
394 | 376 | ||
395 | /* TODO: test this crap and replace bio_map_user with new interface maybe */ | 377 | /* TODO: test this crap and replace bio_map_user with new interface maybe */ |
396 | static int scsi_map_user_pages(struct scsi_tgt_cmd *tcmd, struct scsi_cmnd *cmd, | 378 | static int scsi_map_user_pages(struct scsi_tgt_cmd *tcmd, struct scsi_cmnd *cmd, |
397 | int rw) | 379 | unsigned long uaddr, unsigned int len, int rw) |
398 | { | 380 | { |
399 | struct request_queue *q = cmd->request->q; | 381 | struct request_queue *q = cmd->request->q; |
400 | struct request *rq = cmd->request; | 382 | struct request *rq = cmd->request; |
401 | void *uaddr = tcmd->buffer; | ||
402 | unsigned int len = tcmd->bufflen; | ||
403 | int err; | 383 | int err; |
404 | 384 | ||
405 | dprintk("%lx %u\n", (unsigned long) uaddr, len); | 385 | dprintk("%lx %u\n", uaddr, len); |
406 | err = blk_rq_map_user(q, rq, uaddr, len); | 386 | err = blk_rq_map_user(q, rq, (void *)uaddr, len); |
407 | if (err) { | 387 | if (err) { |
408 | /* | 388 | /* |
409 | * TODO: need to fixup sg_tablesize, max_segment_size, | 389 | * TODO: need to fixup sg_tablesize, max_segment_size, |
@@ -430,45 +410,6 @@ unmap_rq: | |||
430 | return err; | 410 | return err; |
431 | } | 411 | } |
432 | 412 | ||
433 | static int scsi_tgt_transfer_data(struct scsi_cmnd *); | ||
434 | |||
435 | static void scsi_tgt_data_transfer_done(struct scsi_cmnd *cmd) | ||
436 | { | ||
437 | struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; | ||
438 | int err; | ||
439 | |||
440 | /* should we free resources here on error ? */ | ||
441 | if (cmd->result) { | ||
442 | err = scsi_tgt_uspace_send_status(cmd, tcmd->tag); | ||
443 | if (err <= 0) | ||
444 | /* the tgt uspace eh will have to pick this up */ | ||
445 | printk(KERN_ERR "Could not send cmd %p status\n", cmd); | ||
446 | return; | ||
447 | } | ||
448 | |||
449 | dprintk("cmd %p request_bufflen %u bufflen %u\n", | ||
450 | cmd, cmd->request_bufflen, tcmd->bufflen); | ||
451 | |||
452 | scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); | ||
453 | tcmd->buffer += cmd->request_bufflen; | ||
454 | scsi_tgt_transfer_response(cmd); | ||
455 | } | ||
456 | |||
457 | static int scsi_tgt_transfer_data(struct scsi_cmnd *cmd) | ||
458 | { | ||
459 | int err; | ||
460 | struct Scsi_Host *host = scsi_tgt_cmd_to_host(cmd); | ||
461 | |||
462 | err = host->hostt->transfer_data(cmd, scsi_tgt_data_transfer_done); | ||
463 | switch (err) { | ||
464 | case SCSI_MLQUEUE_HOST_BUSY: | ||
465 | case SCSI_MLQUEUE_DEVICE_BUSY: | ||
466 | return -EAGAIN; | ||
467 | default: | ||
468 | return 0; | ||
469 | } | ||
470 | } | ||
471 | |||
472 | static int scsi_tgt_copy_sense(struct scsi_cmnd *cmd, unsigned long uaddr, | 413 | static int scsi_tgt_copy_sense(struct scsi_cmnd *cmd, unsigned long uaddr, |
473 | unsigned len) | 414 | unsigned len) |
474 | { | 415 | { |
@@ -518,8 +459,9 @@ static struct request *tgt_cmd_hash_lookup(struct request_queue *q, u64 tag) | |||
518 | return rq; | 459 | return rq; |
519 | } | 460 | } |
520 | 461 | ||
521 | int scsi_tgt_kspace_exec(int host_no, u64 tag, int result, u32 len, | 462 | int scsi_tgt_kspace_exec(int host_no, int result, u64 tag, |
522 | unsigned long uaddr, u8 rw) | 463 | unsigned long uaddr, u32 len, unsigned long sense_uaddr, |
464 | u32 sense_len, u8 rw) | ||
523 | { | 465 | { |
524 | struct Scsi_Host *shost; | 466 | struct Scsi_Host *shost; |
525 | struct scsi_cmnd *cmd; | 467 | struct scsi_cmnd *cmd; |
@@ -564,36 +506,20 @@ int scsi_tgt_kspace_exec(int host_no, u64 tag, int result, u32 len, | |||
564 | * in the request_* values | 506 | * in the request_* values |
565 | */ | 507 | */ |
566 | tcmd = cmd->request->end_io_data; | 508 | tcmd = cmd->request->end_io_data; |
567 | tcmd->buffer = (void *)uaddr; | ||
568 | tcmd->bufflen = len; | ||
569 | cmd->result = result; | 509 | cmd->result = result; |
570 | 510 | ||
571 | if (!tcmd->bufflen || cmd->request_buffer) { | 511 | if (cmd->result == SAM_STAT_CHECK_CONDITION) |
572 | err = __scsi_tgt_transfer_response(cmd); | 512 | scsi_tgt_copy_sense(cmd, sense_uaddr, sense_len); |
573 | goto done; | ||
574 | } | ||
575 | |||
576 | /* | ||
577 | * TODO: Do we need to handle case where request does not | ||
578 | * align with LLD. | ||
579 | */ | ||
580 | err = scsi_map_user_pages(rq->end_io_data, cmd, rw); | ||
581 | if (err) { | ||
582 | eprintk("%p %d\n", cmd, err); | ||
583 | err = -EAGAIN; | ||
584 | goto done; | ||
585 | } | ||
586 | 513 | ||
587 | /* userspace failure */ | 514 | if (len) { |
588 | if (cmd->result) { | 515 | err = scsi_map_user_pages(rq->end_io_data, cmd, uaddr, len, rw); |
589 | if (status_byte(cmd->result) == CHECK_CONDITION) | 516 | if (err) { |
590 | scsi_tgt_copy_sense(cmd, uaddr, len); | 517 | eprintk("%p %d\n", cmd, err); |
591 | err = __scsi_tgt_transfer_response(cmd); | 518 | err = -EAGAIN; |
592 | goto done; | 519 | goto done; |
520 | } | ||
593 | } | 521 | } |
594 | /* ask the target LLD to transfer the data to the buffer */ | 522 | err = scsi_tgt_transfer_response(cmd); |
595 | err = scsi_tgt_transfer_data(cmd); | ||
596 | |||
597 | done: | 523 | done: |
598 | scsi_host_put(shost); | 524 | scsi_host_put(shost); |
599 | return err; | 525 | return err; |
diff --git a/drivers/scsi/scsi_tgt_priv.h b/drivers/scsi/scsi_tgt_priv.h index 84488c51ff62..e9e6db1c417f 100644 --- a/drivers/scsi/scsi_tgt_priv.h +++ b/drivers/scsi/scsi_tgt_priv.h | |||
@@ -18,8 +18,9 @@ extern int scsi_tgt_if_init(void); | |||
18 | extern int scsi_tgt_uspace_send_cmd(struct scsi_cmnd *cmd, struct scsi_lun *lun, | 18 | extern int scsi_tgt_uspace_send_cmd(struct scsi_cmnd *cmd, struct scsi_lun *lun, |
19 | u64 tag); | 19 | u64 tag); |
20 | extern int scsi_tgt_uspace_send_status(struct scsi_cmnd *cmd, u64 tag); | 20 | extern int scsi_tgt_uspace_send_status(struct scsi_cmnd *cmd, u64 tag); |
21 | extern int scsi_tgt_kspace_exec(int host_no, u64 tag, int result, u32 len, | 21 | extern int scsi_tgt_kspace_exec(int host_no, int result, u64 tag, |
22 | unsigned long uaddr, u8 rw); | 22 | unsigned long uaddr, u32 len, unsigned long sense_uaddr, |
23 | u32 sense_len, u8 rw); | ||
23 | extern int scsi_tgt_uspace_send_tsk_mgmt(int host_no, int function, u64 tag, | 24 | extern int scsi_tgt_uspace_send_tsk_mgmt(int host_no, int function, u64 tag, |
24 | struct scsi_lun *scsilun, void *data); | 25 | struct scsi_lun *scsilun, void *data); |
25 | extern int scsi_tgt_kspace_tsk_mgmt(int host_no, u64 mid, int result); | 26 | extern int scsi_tgt_kspace_tsk_mgmt(int host_no, u64 mid, int result); |
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 7f1f411d07af..965b6b8ffec5 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h | |||
@@ -129,6 +129,11 @@ struct scsi_host_template { | |||
129 | * the LLD. When the driver is finished processing the command | 129 | * the LLD. When the driver is finished processing the command |
130 | * the done callback is invoked. | 130 | * the done callback is invoked. |
131 | * | 131 | * |
132 | * This is called to inform the LLD to transfer | ||
133 | * cmd->request_bufflen bytes. The cmd->use_sg speciefies the | ||
134 | * number of scatterlist entried in the command and | ||
135 | * cmd->request_buffer contains the scatterlist. | ||
136 | * | ||
132 | * return values: see queuecommand | 137 | * return values: see queuecommand |
133 | * | 138 | * |
134 | * If the LLD accepts the cmd, it should set the result to an | 139 | * If the LLD accepts the cmd, it should set the result to an |
@@ -139,20 +144,6 @@ struct scsi_host_template { | |||
139 | /* TODO: rename */ | 144 | /* TODO: rename */ |
140 | int (* transfer_response)(struct scsi_cmnd *, | 145 | int (* transfer_response)(struct scsi_cmnd *, |
141 | void (*done)(struct scsi_cmnd *)); | 146 | void (*done)(struct scsi_cmnd *)); |
142 | /* | ||
143 | * This is called to inform the LLD to transfer cmd->request_bufflen | ||
144 | * bytes of the cmd at cmd->offset in the cmd. The cmd->use_sg | ||
145 | * speciefies the number of scatterlist entried in the command | ||
146 | * and cmd->request_buffer contains the scatterlist. | ||
147 | * | ||
148 | * If the command cannot be processed in one transfer_data call | ||
149 | * becuase a scatterlist within the LLD's limits cannot be | ||
150 | * created then transfer_data will be called multiple times. | ||
151 | * It is initially called from process context, and later | ||
152 | * calls are from the interrup context. | ||
153 | */ | ||
154 | int (* transfer_data)(struct scsi_cmnd *, | ||
155 | void (*done)(struct scsi_cmnd *)); | ||
156 | 147 | ||
157 | /* Used as callback for the completion of task management request. */ | 148 | /* Used as callback for the completion of task management request. */ |
158 | int (* tsk_mgmt_response)(u64 mid, int result); | 149 | int (* tsk_mgmt_response)(u64 mid, int result); |
diff --git a/include/scsi/scsi_tgt_if.h b/include/scsi/scsi_tgt_if.h index 07d6e77ae895..4cf9dff29a2f 100644 --- a/include/scsi/scsi_tgt_if.h +++ b/include/scsi/scsi_tgt_if.h | |||
@@ -45,11 +45,13 @@ struct tgt_event { | |||
45 | /* user-> kernel */ | 45 | /* user-> kernel */ |
46 | struct { | 46 | struct { |
47 | int host_no; | 47 | int host_no; |
48 | uint32_t len; | ||
49 | int result; | 48 | int result; |
49 | aligned_u64 tag; | ||
50 | aligned_u64 uaddr; | 50 | aligned_u64 uaddr; |
51 | aligned_u64 sense_uaddr; | ||
52 | uint32_t len; | ||
53 | uint32_t sense_len; | ||
51 | uint8_t rw; | 54 | uint8_t rw; |
52 | aligned_u64 tag; | ||
53 | } cmd_rsp; | 55 | } cmd_rsp; |
54 | struct { | 56 | struct { |
55 | int host_no; | 57 | int host_no; |