diff options
| author | Mike Christie <michaelc@cs.wisc.edu> | 2008-05-21 16:54:07 -0400 |
|---|---|---|
| committer | James Bottomley <James.Bottomley@HansenPartnership.com> | 2008-07-12 09:22:19 -0400 |
| commit | fbc514b4e262bc0596faae8640ebc0b9142a1cdd (patch) | |
| tree | 74910fb5c2b2475742d13f4ac44e5eddf7aad81a /drivers/scsi | |
| parent | 3e5c28ad0391389959ccae81c938c7533efb3490 (diff) | |
[SCSI] iscsi_tcp: convert iscsi_tcp to support merged tasks
Convert iscsi_tcp to support merged tasks.
Signed-off-by: Mike Christie <michaelc@cs.wisc.edu>
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Diffstat (limited to 'drivers/scsi')
| -rw-r--r-- | drivers/scsi/iscsi_tcp.c | 130 | ||||
| -rw-r--r-- | drivers/scsi/iscsi_tcp.h | 5 |
2 files changed, 54 insertions, 81 deletions
diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c index f2a08f7ed902..517bad160bea 100644 --- a/drivers/scsi/iscsi_tcp.c +++ b/drivers/scsi/iscsi_tcp.c | |||
| @@ -498,11 +498,15 @@ iscsi_tcp_data_recv_prep(struct iscsi_tcp_conn *tcp_conn) | |||
| 498 | * must be called with session lock | 498 | * must be called with session lock |
| 499 | */ | 499 | */ |
| 500 | static void | 500 | static void |
| 501 | iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) | 501 | iscsi_tcp_cleanup_task(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) |
| 502 | { | 502 | { |
| 503 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; | 503 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; |
| 504 | struct iscsi_r2t_info *r2t; | 504 | struct iscsi_r2t_info *r2t; |
| 505 | 505 | ||
| 506 | /* nothing to do for mgmt ctasks */ | ||
| 507 | if (!ctask->sc) | ||
| 508 | return; | ||
| 509 | |||
| 506 | /* flush ctask's r2t queues */ | 510 | /* flush ctask's r2t queues */ |
| 507 | while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*))) { | 511 | while (__kfifo_get(tcp_ctask->r2tqueue, (void*)&r2t, sizeof(void*))) { |
| 508 | __kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t, | 512 | __kfifo_put(tcp_ctask->r2tpool.queue, (void*)&r2t, |
| @@ -521,7 +525,7 @@ iscsi_tcp_cleanup_ctask(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) | |||
| 521 | /** | 525 | /** |
| 522 | * iscsi_data_rsp - SCSI Data-In Response processing | 526 | * iscsi_data_rsp - SCSI Data-In Response processing |
| 523 | * @conn: iscsi connection | 527 | * @conn: iscsi connection |
| 524 | * @ctask: scsi command task | 528 | * @ctask: scsi command ctask |
| 525 | **/ | 529 | **/ |
| 526 | static int | 530 | static int |
| 527 | iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) | 531 | iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) |
| @@ -578,7 +582,7 @@ iscsi_data_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) | |||
| 578 | /** | 582 | /** |
| 579 | * iscsi_solicit_data_init - initialize first Data-Out | 583 | * iscsi_solicit_data_init - initialize first Data-Out |
| 580 | * @conn: iscsi connection | 584 | * @conn: iscsi connection |
| 581 | * @ctask: scsi command task | 585 | * @ctask: scsi command ctask |
| 582 | * @r2t: R2T info | 586 | * @r2t: R2T info |
| 583 | * | 587 | * |
| 584 | * Notes: | 588 | * Notes: |
| @@ -620,7 +624,7 @@ iscsi_solicit_data_init(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask, | |||
| 620 | /** | 624 | /** |
| 621 | * iscsi_r2t_rsp - iSCSI R2T Response processing | 625 | * iscsi_r2t_rsp - iSCSI R2T Response processing |
| 622 | * @conn: iscsi connection | 626 | * @conn: iscsi connection |
| 623 | * @ctask: scsi command task | 627 | * @ctask: scsi command ctask |
| 624 | **/ | 628 | **/ |
| 625 | static int | 629 | static int |
| 626 | iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) | 630 | iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) |
| @@ -646,7 +650,7 @@ iscsi_r2t_rsp(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) | |||
| 646 | return ISCSI_ERR_R2TSN; | 650 | return ISCSI_ERR_R2TSN; |
| 647 | } | 651 | } |
| 648 | 652 | ||
| 649 | /* fill-in new R2T associated with the task */ | 653 | /* fill-in new R2T associated with the ctask */ |
| 650 | iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr); | 654 | iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr); |
| 651 | 655 | ||
| 652 | if (!ctask->sc || session->state != ISCSI_STATE_LOGGED_IN) { | 656 | if (!ctask->sc || session->state != ISCSI_STATE_LOGGED_IN) { |
| @@ -769,6 +773,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr) | |||
| 769 | ctask = iscsi_itt_to_ctask(conn, hdr->itt); | 773 | ctask = iscsi_itt_to_ctask(conn, hdr->itt); |
| 770 | if (!ctask) | 774 | if (!ctask) |
| 771 | return ISCSI_ERR_BAD_ITT; | 775 | return ISCSI_ERR_BAD_ITT; |
| 776 | if (!ctask->sc) | ||
| 777 | return ISCSI_ERR_NO_SCSI_CMD; | ||
| 772 | 778 | ||
| 773 | spin_lock(&conn->session->lock); | 779 | spin_lock(&conn->session->lock); |
| 774 | rc = iscsi_data_rsp(conn, ctask); | 780 | rc = iscsi_data_rsp(conn, ctask); |
| @@ -815,6 +821,8 @@ iscsi_tcp_hdr_dissect(struct iscsi_conn *conn, struct iscsi_hdr *hdr) | |||
| 815 | ctask = iscsi_itt_to_ctask(conn, hdr->itt); | 821 | ctask = iscsi_itt_to_ctask(conn, hdr->itt); |
| 816 | if (!ctask) | 822 | if (!ctask) |
| 817 | return ISCSI_ERR_BAD_ITT; | 823 | return ISCSI_ERR_BAD_ITT; |
| 824 | if (!ctask->sc) | ||
| 825 | return ISCSI_ERR_NO_SCSI_CMD; | ||
| 818 | 826 | ||
| 819 | if (ahslen) | 827 | if (ahslen) |
| 820 | rc = ISCSI_ERR_AHSLEN; | 828 | rc = ISCSI_ERR_AHSLEN; |
| @@ -1194,7 +1202,7 @@ iscsi_tcp_send_hdr_prep(struct iscsi_conn *conn, void *hdr, size_t hdrlen) | |||
| 1194 | 1202 | ||
| 1195 | /* If header digest is enabled, compute the CRC and | 1203 | /* If header digest is enabled, compute the CRC and |
| 1196 | * place the digest into the same buffer. We make | 1204 | * place the digest into the same buffer. We make |
| 1197 | * sure that both iscsi_tcp_ctask and mtask have | 1205 | * sure that both iscsi_tcp_cmd_task and mctask have |
| 1198 | * sufficient room. | 1206 | * sufficient room. |
| 1199 | */ | 1207 | */ |
| 1200 | if (conn->hdrdgst_en) { | 1208 | if (conn->hdrdgst_en) { |
| @@ -1269,7 +1277,7 @@ iscsi_tcp_send_linear_data_prepare(struct iscsi_conn *conn, void *data, | |||
| 1269 | /** | 1277 | /** |
| 1270 | * iscsi_solicit_data_cont - initialize next Data-Out | 1278 | * iscsi_solicit_data_cont - initialize next Data-Out |
| 1271 | * @conn: iscsi connection | 1279 | * @conn: iscsi connection |
| 1272 | * @ctask: scsi command task | 1280 | * @ctask: scsi command ctask |
| 1273 | * @r2t: R2T info | 1281 | * @r2t: R2T info |
| 1274 | * @left: bytes left to transfer | 1282 | * @left: bytes left to transfer |
| 1275 | * | 1283 | * |
| @@ -1316,19 +1324,37 @@ iscsi_solicit_data_cont(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask, | |||
| 1316 | } | 1324 | } |
| 1317 | 1325 | ||
| 1318 | /** | 1326 | /** |
| 1319 | * iscsi_tcp_ctask - Initialize iSCSI SCSI_READ or SCSI_WRITE commands | 1327 | * iscsi_tcp_task - Initialize iSCSI SCSI_READ or SCSI_WRITE commands |
| 1320 | * @conn: iscsi connection | 1328 | * @conn: iscsi connection |
| 1321 | * @ctask: scsi command task | 1329 | * @ctask: scsi command ctask |
| 1322 | * @sc: scsi command | 1330 | * @sc: scsi command |
| 1323 | **/ | 1331 | **/ |
| 1324 | static int | 1332 | static int |
| 1325 | iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask) | 1333 | iscsi_tcp_task_init(struct iscsi_cmd_task *ctask) |
| 1326 | { | 1334 | { |
| 1327 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; | 1335 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; |
| 1328 | struct iscsi_conn *conn = ctask->conn; | 1336 | struct iscsi_conn *conn = ctask->conn; |
| 1329 | struct scsi_cmnd *sc = ctask->sc; | 1337 | struct scsi_cmnd *sc = ctask->sc; |
| 1330 | int err; | 1338 | int err; |
| 1331 | 1339 | ||
| 1340 | if (!sc) { | ||
| 1341 | /* | ||
| 1342 | * mgmt ctasks do not have a scatterlist since they come | ||
| 1343 | * in from the iscsi interface. | ||
| 1344 | */ | ||
| 1345 | debug_scsi("mctask deq [cid %d itt 0x%x]\n", conn->id, | ||
| 1346 | ctask->itt); | ||
| 1347 | |||
| 1348 | /* Prepare PDU, optionally w/ immediate data */ | ||
| 1349 | iscsi_tcp_send_hdr_prep(conn, ctask->hdr, sizeof(*ctask->hdr)); | ||
| 1350 | |||
| 1351 | /* If we have immediate data, attach a payload */ | ||
| 1352 | if (ctask->data_count) | ||
| 1353 | iscsi_tcp_send_linear_data_prepare(conn, ctask->data, | ||
| 1354 | ctask->data_count); | ||
| 1355 | return 0; | ||
| 1356 | } | ||
| 1357 | |||
| 1332 | BUG_ON(__kfifo_len(tcp_ctask->r2tqueue)); | 1358 | BUG_ON(__kfifo_len(tcp_ctask->r2tqueue)); |
| 1333 | tcp_ctask->sent = 0; | 1359 | tcp_ctask->sent = 0; |
| 1334 | tcp_ctask->exp_datasn = 0; | 1360 | tcp_ctask->exp_datasn = 0; |
| @@ -1353,52 +1379,21 @@ iscsi_tcp_ctask_init(struct iscsi_cmd_task *ctask) | |||
| 1353 | return 0; | 1379 | return 0; |
| 1354 | } | 1380 | } |
| 1355 | 1381 | ||
| 1356 | /** | ||
| 1357 | * iscsi_tcp_mtask_xmit - xmit management(immediate) task | ||
| 1358 | * @conn: iscsi connection | ||
| 1359 | * @mtask: task management task | ||
| 1360 | * | ||
| 1361 | * Notes: | ||
| 1362 | * The function can return -EAGAIN in which case caller must | ||
| 1363 | * call it again later, or recover. '0' return code means successful | ||
| 1364 | * xmit. | ||
| 1365 | **/ | ||
| 1366 | static int | ||
| 1367 | iscsi_tcp_mtask_xmit(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask) | ||
| 1368 | { | ||
| 1369 | int rc; | ||
| 1370 | |||
| 1371 | /* Flush any pending data first. */ | ||
| 1372 | rc = iscsi_tcp_flush(conn); | ||
| 1373 | if (rc < 0) | ||
| 1374 | return rc; | ||
| 1375 | |||
| 1376 | if (mtask->hdr->itt == RESERVED_ITT) { | ||
| 1377 | struct iscsi_session *session = conn->session; | ||
| 1378 | |||
| 1379 | spin_lock_bh(&session->lock); | ||
| 1380 | iscsi_free_mgmt_task(conn, mtask); | ||
| 1381 | spin_unlock_bh(&session->lock); | ||
| 1382 | } | ||
| 1383 | |||
| 1384 | return 0; | ||
| 1385 | } | ||
| 1386 | |||
| 1387 | /* | 1382 | /* |
| 1388 | * iscsi_tcp_ctask_xmit - xmit normal PDU task | 1383 | * iscsi_tcp_task_xmit - xmit normal PDU ctask |
| 1389 | * @conn: iscsi connection | 1384 | * @ctask: iscsi command ctask |
| 1390 | * @ctask: iscsi command task | ||
| 1391 | * | 1385 | * |
| 1392 | * We're expected to return 0 when everything was transmitted succesfully, | 1386 | * We're expected to return 0 when everything was transmitted succesfully, |
| 1393 | * -EAGAIN if there's still data in the queue, or != 0 for any other kind | 1387 | * -EAGAIN if there's still data in the queue, or != 0 for any other kind |
| 1394 | * of error. | 1388 | * of error. |
| 1395 | */ | 1389 | */ |
| 1396 | static int | 1390 | static int |
| 1397 | iscsi_tcp_ctask_xmit(struct iscsi_conn *conn, struct iscsi_cmd_task *ctask) | 1391 | iscsi_tcp_task_xmit(struct iscsi_cmd_task *ctask) |
| 1398 | { | 1392 | { |
| 1393 | struct iscsi_conn *conn = ctask->conn; | ||
| 1399 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; | 1394 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; |
| 1400 | struct scsi_cmnd *sc = ctask->sc; | 1395 | struct scsi_cmnd *sc = ctask->sc; |
| 1401 | struct scsi_data_buffer *sdb = scsi_out(sc); | 1396 | struct scsi_data_buffer *sdb; |
| 1402 | int rc = 0; | 1397 | int rc = 0; |
| 1403 | 1398 | ||
| 1404 | flush: | 1399 | flush: |
| @@ -1407,10 +1402,18 @@ flush: | |||
| 1407 | if (rc < 0) | 1402 | if (rc < 0) |
| 1408 | return rc; | 1403 | return rc; |
| 1409 | 1404 | ||
| 1405 | /* mgmt command */ | ||
| 1406 | if (!sc) { | ||
| 1407 | if (ctask->hdr->itt == RESERVED_ITT) | ||
| 1408 | iscsi_put_ctask(ctask); | ||
| 1409 | return 0; | ||
| 1410 | } | ||
| 1411 | |||
| 1410 | /* Are we done already? */ | 1412 | /* Are we done already? */ |
| 1411 | if (sc->sc_data_direction != DMA_TO_DEVICE) | 1413 | if (sc->sc_data_direction != DMA_TO_DEVICE) |
| 1412 | return 0; | 1414 | return 0; |
| 1413 | 1415 | ||
| 1416 | sdb = scsi_out(sc); | ||
| 1414 | if (ctask->unsol_count != 0) { | 1417 | if (ctask->unsol_count != 0) { |
| 1415 | struct iscsi_data *hdr = &tcp_ctask->unsol_dtask.hdr; | 1418 | struct iscsi_data *hdr = &tcp_ctask->unsol_dtask.hdr; |
| 1416 | 1419 | ||
| @@ -1688,21 +1691,6 @@ free_socket: | |||
| 1688 | return err; | 1691 | return err; |
| 1689 | } | 1692 | } |
| 1690 | 1693 | ||
| 1691 | /* called with host lock */ | ||
| 1692 | static void | ||
| 1693 | iscsi_tcp_mtask_init(struct iscsi_conn *conn, struct iscsi_mgmt_task *mtask) | ||
| 1694 | { | ||
| 1695 | debug_scsi("mtask deq [cid %d itt 0x%x]\n", conn->id, mtask->itt); | ||
| 1696 | |||
| 1697 | /* Prepare PDU, optionally w/ immediate data */ | ||
| 1698 | iscsi_tcp_send_hdr_prep(conn, mtask->hdr, sizeof(*mtask->hdr)); | ||
| 1699 | |||
| 1700 | /* If we have immediate data, attach a payload */ | ||
| 1701 | if (mtask->data_count) | ||
| 1702 | iscsi_tcp_send_linear_data_prepare(conn, mtask->data, | ||
| 1703 | mtask->data_count); | ||
| 1704 | } | ||
| 1705 | |||
| 1706 | static int | 1694 | static int |
| 1707 | iscsi_r2tpool_alloc(struct iscsi_session *session) | 1695 | iscsi_r2tpool_alloc(struct iscsi_session *session) |
| 1708 | { | 1696 | { |
| @@ -1710,7 +1698,7 @@ iscsi_r2tpool_alloc(struct iscsi_session *session) | |||
| 1710 | int cmd_i; | 1698 | int cmd_i; |
| 1711 | 1699 | ||
| 1712 | /* | 1700 | /* |
| 1713 | * initialize per-task: R2T pool and xmit queue | 1701 | * initialize per-ctask: R2T pool and xmit queue |
| 1714 | */ | 1702 | */ |
| 1715 | for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { | 1703 | for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { |
| 1716 | struct iscsi_cmd_task *ctask = session->cmds[cmd_i]; | 1704 | struct iscsi_cmd_task *ctask = session->cmds[cmd_i]; |
| @@ -1880,13 +1868,12 @@ iscsi_tcp_session_create(struct Scsi_Host *shost, uint16_t cmds_max, | |||
| 1880 | 1868 | ||
| 1881 | cls_session = iscsi_session_setup(&iscsi_tcp_transport, shost, cmds_max, | 1869 | cls_session = iscsi_session_setup(&iscsi_tcp_transport, shost, cmds_max, |
| 1882 | sizeof(struct iscsi_tcp_cmd_task), | 1870 | sizeof(struct iscsi_tcp_cmd_task), |
| 1883 | sizeof(struct iscsi_tcp_mgmt_task), | ||
| 1884 | initial_cmdsn); | 1871 | initial_cmdsn); |
| 1885 | if (!cls_session) | 1872 | if (!cls_session) |
| 1886 | goto remove_host; | 1873 | goto remove_host; |
| 1887 | session = cls_session->dd_data; | 1874 | session = cls_session->dd_data; |
| 1888 | 1875 | ||
| 1889 | shost->can_queue = session->cmds_max; | 1876 | shost->can_queue = session->scsi_cmds_max; |
| 1890 | for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { | 1877 | for (cmd_i = 0; cmd_i < session->cmds_max; cmd_i++) { |
| 1891 | struct iscsi_cmd_task *ctask = session->cmds[cmd_i]; | 1878 | struct iscsi_cmd_task *ctask = session->cmds[cmd_i]; |
| 1892 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; | 1879 | struct iscsi_tcp_cmd_task *tcp_ctask = ctask->dd_data; |
| @@ -1895,13 +1882,6 @@ iscsi_tcp_session_create(struct Scsi_Host *shost, uint16_t cmds_max, | |||
| 1895 | ctask->hdr_max = sizeof(tcp_ctask->hdr) - ISCSI_DIGEST_SIZE; | 1882 | ctask->hdr_max = sizeof(tcp_ctask->hdr) - ISCSI_DIGEST_SIZE; |
| 1896 | } | 1883 | } |
| 1897 | 1884 | ||
| 1898 | for (cmd_i = 0; cmd_i < session->mgmtpool_max; cmd_i++) { | ||
| 1899 | struct iscsi_mgmt_task *mtask = session->mgmt_cmds[cmd_i]; | ||
| 1900 | struct iscsi_tcp_mgmt_task *tcp_mtask = mtask->dd_data; | ||
| 1901 | |||
| 1902 | mtask->hdr = (struct iscsi_hdr *) &tcp_mtask->hdr; | ||
| 1903 | } | ||
| 1904 | |||
| 1905 | if (iscsi_r2tpool_alloc(session)) | 1885 | if (iscsi_r2tpool_alloc(session)) |
| 1906 | goto remove_session; | 1886 | goto remove_session; |
| 1907 | return cls_session; | 1887 | return cls_session; |
| @@ -1999,11 +1979,9 @@ static struct iscsi_transport iscsi_tcp_transport = { | |||
| 1999 | /* IO */ | 1979 | /* IO */ |
| 2000 | .send_pdu = iscsi_conn_send_pdu, | 1980 | .send_pdu = iscsi_conn_send_pdu, |
| 2001 | .get_stats = iscsi_conn_get_stats, | 1981 | .get_stats = iscsi_conn_get_stats, |
| 2002 | .init_cmd_task = iscsi_tcp_ctask_init, | 1982 | .init_task = iscsi_tcp_task_init, |
| 2003 | .init_mgmt_task = iscsi_tcp_mtask_init, | 1983 | .xmit_task = iscsi_tcp_task_xmit, |
| 2004 | .xmit_cmd_task = iscsi_tcp_ctask_xmit, | 1984 | .cleanup_task = iscsi_tcp_cleanup_task, |
| 2005 | .xmit_mgmt_task = iscsi_tcp_mtask_xmit, | ||
| 2006 | .cleanup_cmd_task = iscsi_tcp_cleanup_ctask, | ||
| 2007 | /* recovery */ | 1985 | /* recovery */ |
| 2008 | .session_recovery_timedout = iscsi_session_recovery_timedout, | 1986 | .session_recovery_timedout = iscsi_session_recovery_timedout, |
| 2009 | }; | 1987 | }; |
diff --git a/drivers/scsi/iscsi_tcp.h b/drivers/scsi/iscsi_tcp.h index ed0b991d1e72..c9c8633c41a6 100644 --- a/drivers/scsi/iscsi_tcp.h +++ b/drivers/scsi/iscsi_tcp.h | |||
| @@ -103,11 +103,6 @@ struct iscsi_data_task { | |||
| 103 | char hdrext[ISCSI_DIGEST_SIZE];/* Header-Digest */ | 103 | char hdrext[ISCSI_DIGEST_SIZE];/* Header-Digest */ |
| 104 | }; | 104 | }; |
| 105 | 105 | ||
| 106 | struct iscsi_tcp_mgmt_task { | ||
| 107 | struct iscsi_hdr hdr; | ||
| 108 | char hdrext[ISCSI_DIGEST_SIZE]; /* Header-Digest */ | ||
| 109 | }; | ||
| 110 | |||
| 111 | struct iscsi_r2t_info { | 106 | struct iscsi_r2t_info { |
| 112 | __be32 ttt; /* copied from R2T */ | 107 | __be32 ttt; /* copied from R2T */ |
| 113 | __be32 exp_statsn; /* copied from R2T */ | 108 | __be32 exp_statsn; /* copied from R2T */ |
