diff options
author | Serge Semin <fancer.lancer@gmail.com> | 2017-01-11 03:11:33 +0300 |
---|---|---|
committer | Jon Mason <jdmason@kudzu.us> | 2017-07-06 11:30:07 -0400 |
commit | 443b9a14ecbe811071467d54d6f2f1182835cc4d (patch) | |
tree | 240003b0193c3ba06090b13479acc949ceb36640 /drivers/ntb | |
parent | 4e8c11b7fd29f70eb7af43bae908297689f2c3da (diff) | |
download | blackbird-obmc-linux-443b9a14ecbe811071467d54d6f2f1182835cc4d.tar.gz blackbird-obmc-linux-443b9a14ecbe811071467d54d6f2f1182835cc4d.zip |
NTB: Alter MW API to support multi-ports devices
Multi-port NTB devices permit to share a memory between all accessible peers.
Memory Windows API is altered to correspondingly initialize and map memory
windows for such devices:
ntb_mw_count(pidx); - number of inbound memory windows, which can be allocated
for shared buffer with specified peer device.
ntb_mw_get_align(pidx, widx); - get alignment and size restriction parameters
to properly allocate inbound memory region.
ntb_peer_mw_count(); - get number of outbound memory windows.
ntb_peer_mw_get_addr(widx); - get mapping address of an outbound memory window
If hardware supports inbound translation configured on the local ntb port:
ntb_mw_set_trans(pidx, widx); - set translation address of allocated inbound
memory window so a peer device could access it.
ntb_mw_clear_trans(pidx, widx); - clear the translation address of an inbound
memory window.
If hardware supports outbound translation configured on the peer ntb port:
ntb_peer_mw_set_trans(pidx, widx); - set translation address of a memory
window retrieved from a peer device
ntb_peer_mw_clear_trans(pidx, widx); - clear the translation address of an
outbound memory window
Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
Acked-by: Allen Hubbe <Allen.Hubbe@dell.com>
Signed-off-by: Jon Mason <jdmason@kudzu.us>
Diffstat (limited to 'drivers/ntb')
-rw-r--r-- | drivers/ntb/hw/amd/ntb_hw_amd.c | 68 | ||||
-rw-r--r-- | drivers/ntb/hw/intel/ntb_hw_intel.c | 90 | ||||
-rw-r--r-- | drivers/ntb/ntb.c | 2 | ||||
-rw-r--r-- | drivers/ntb/ntb_transport.c | 21 | ||||
-rw-r--r-- | drivers/ntb/test/ntb_perf.c | 17 | ||||
-rw-r--r-- | drivers/ntb/test/ntb_tool.c | 43 |
6 files changed, 179 insertions, 62 deletions
diff --git a/drivers/ntb/hw/amd/ntb_hw_amd.c b/drivers/ntb/hw/amd/ntb_hw_amd.c index e71ab4c1fe0e..bcefe1df9ce3 100644 --- a/drivers/ntb/hw/amd/ntb_hw_amd.c +++ b/drivers/ntb/hw/amd/ntb_hw_amd.c @@ -5,6 +5,7 @@ * GPL LICENSE SUMMARY * * Copyright (C) 2016 Advanced Micro Devices, Inc. All Rights Reserved. + * Copyright (C) 2016 T-Platforms. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as @@ -13,6 +14,7 @@ * BSD LICENSE * * Copyright (C) 2016 Advanced Micro Devices, Inc. All Rights Reserved. + * Copyright (C) 2016 T-Platforms. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -79,40 +81,42 @@ static int ndev_mw_to_bar(struct amd_ntb_dev *ndev, int idx) return 1 << idx; } -static int amd_ntb_mw_count(struct ntb_dev *ntb) +static int amd_ntb_mw_count(struct ntb_dev *ntb, int pidx) { + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + return ntb_ndev(ntb)->mw_count; } -static int amd_ntb_mw_get_range(struct ntb_dev *ntb, int idx, - phys_addr_t *base, - resource_size_t *size, - resource_size_t *align, - resource_size_t *align_size) +static int amd_ntb_mw_get_align(struct ntb_dev *ntb, int pidx, int idx, + resource_size_t *addr_align, + resource_size_t *size_align, + resource_size_t *size_max) { struct amd_ntb_dev *ndev = ntb_ndev(ntb); int bar; + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + bar = ndev_mw_to_bar(ndev, idx); if (bar < 0) return bar; - if (base) - *base = pci_resource_start(ndev->ntb.pdev, bar); - - if (size) - *size = pci_resource_len(ndev->ntb.pdev, bar); + if (addr_align) + *addr_align = SZ_4K; - if (align) - *align = SZ_4K; + if (size_align) + *size_align = 1; - if (align_size) - *align_size = 1; + if (size_max) + *size_max = pci_resource_len(ndev->ntb.pdev, bar); return 0; } -static int amd_ntb_mw_set_trans(struct ntb_dev *ntb, int idx, +static int amd_ntb_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx, dma_addr_t addr, resource_size_t size) { struct amd_ntb_dev *ndev = ntb_ndev(ntb); @@ -122,6 +126,9 @@ static int amd_ntb_mw_set_trans(struct ntb_dev *ntb, int idx, u64 base_addr, limit, reg_val; int bar; + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + bar = ndev_mw_to_bar(ndev, idx); if (bar < 0) return bar; @@ -284,6 +291,31 @@ static int amd_ntb_link_disable(struct ntb_dev *ntb) return 0; } +static int amd_ntb_peer_mw_count(struct ntb_dev *ntb) +{ + /* The same as for inbound MWs */ + return ntb_ndev(ntb)->mw_count; +} + +static int amd_ntb_peer_mw_get_addr(struct ntb_dev *ntb, int idx, + phys_addr_t *base, resource_size_t *size) +{ + struct amd_ntb_dev *ndev = ntb_ndev(ntb); + int bar; + + bar = ndev_mw_to_bar(ndev, idx); + if (bar < 0) + return bar; + + if (base) + *base = pci_resource_start(ndev->ntb.pdev, bar); + + if (size) + *size = pci_resource_len(ndev->ntb.pdev, bar); + + return 0; +} + static u64 amd_ntb_db_valid_mask(struct ntb_dev *ntb) { return ntb_ndev(ntb)->db_valid_mask; @@ -431,8 +463,10 @@ static int amd_ntb_peer_spad_write(struct ntb_dev *ntb, static const struct ntb_dev_ops amd_ntb_ops = { .mw_count = amd_ntb_mw_count, - .mw_get_range = amd_ntb_mw_get_range, + .mw_get_align = amd_ntb_mw_get_align, .mw_set_trans = amd_ntb_mw_set_trans, + .peer_mw_count = amd_ntb_peer_mw_count, + .peer_mw_get_addr = amd_ntb_peer_mw_get_addr, .link_is_up = amd_ntb_link_is_up, .link_enable = amd_ntb_link_enable, .link_disable = amd_ntb_link_disable, diff --git a/drivers/ntb/hw/intel/ntb_hw_intel.c b/drivers/ntb/hw/intel/ntb_hw_intel.c index 6b25bf816f28..a9b4ed4d7b52 100644 --- a/drivers/ntb/hw/intel/ntb_hw_intel.c +++ b/drivers/ntb/hw/intel/ntb_hw_intel.c @@ -6,6 +6,7 @@ * * Copyright(c) 2012 Intel Corporation. All rights reserved. * Copyright (C) 2015 EMC Corporation. All Rights Reserved. + * Copyright (C) 2016 T-Platforms. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as @@ -15,6 +16,7 @@ * * Copyright(c) 2012 Intel Corporation. All rights reserved. * Copyright (C) 2015 EMC Corporation. All Rights Reserved. + * Copyright (C) 2016 T-Platforms. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -1035,20 +1037,26 @@ static void ndev_deinit_debugfs(struct intel_ntb_dev *ndev) debugfs_remove_recursive(ndev->debugfs_dir); } -static int intel_ntb_mw_count(struct ntb_dev *ntb) +static int intel_ntb_mw_count(struct ntb_dev *ntb, int pidx) { + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + return ntb_ndev(ntb)->mw_count; } -static int intel_ntb_mw_get_range(struct ntb_dev *ntb, int idx, - phys_addr_t *base, - resource_size_t *size, - resource_size_t *align, - resource_size_t *align_size) +static int intel_ntb_mw_get_align(struct ntb_dev *ntb, int pidx, int idx, + resource_size_t *addr_align, + resource_size_t *size_align, + resource_size_t *size_max) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); + resource_size_t bar_size, mw_size; int bar; + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + if (idx >= ndev->b2b_idx && !ndev->b2b_off) idx += 1; @@ -1056,24 +1064,26 @@ static int intel_ntb_mw_get_range(struct ntb_dev *ntb, int idx, if (bar < 0) return bar; - if (base) - *base = pci_resource_start(ndev->ntb.pdev, bar) + - (idx == ndev->b2b_idx ? ndev->b2b_off : 0); + bar_size = pci_resource_len(ndev->ntb.pdev, bar); - if (size) - *size = pci_resource_len(ndev->ntb.pdev, bar) - - (idx == ndev->b2b_idx ? ndev->b2b_off : 0); + if (idx == ndev->b2b_idx) + mw_size = bar_size - ndev->b2b_off; + else + mw_size = bar_size; + + if (addr_align) + *addr_align = pci_resource_len(ndev->ntb.pdev, bar); - if (align) - *align = pci_resource_len(ndev->ntb.pdev, bar); + if (size_align) + *size_align = 1; - if (align_size) - *align_size = 1; + if (size_max) + *size_max = mw_size; return 0; } -static int intel_ntb_mw_set_trans(struct ntb_dev *ntb, int idx, +static int intel_ntb_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx, dma_addr_t addr, resource_size_t size) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1083,6 +1093,9 @@ static int intel_ntb_mw_set_trans(struct ntb_dev *ntb, int idx, u64 base, limit, reg_val; int bar; + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + if (idx >= ndev->b2b_idx && !ndev->b2b_off) idx += 1; @@ -1249,6 +1262,36 @@ static int intel_ntb_link_disable(struct ntb_dev *ntb) return 0; } +static int intel_ntb_peer_mw_count(struct ntb_dev *ntb) +{ + /* Numbers of inbound and outbound memory windows match */ + return ntb_ndev(ntb)->mw_count; +} + +static int intel_ntb_peer_mw_get_addr(struct ntb_dev *ntb, int idx, + phys_addr_t *base, resource_size_t *size) +{ + struct intel_ntb_dev *ndev = ntb_ndev(ntb); + int bar; + + if (idx >= ndev->b2b_idx && !ndev->b2b_off) + idx += 1; + + bar = ndev_mw_to_bar(ndev, idx); + if (bar < 0) + return bar; + + if (base) + *base = pci_resource_start(ndev->ntb.pdev, bar) + + (idx == ndev->b2b_idx ? ndev->b2b_off : 0); + + if (size) + *size = pci_resource_len(ndev->ntb.pdev, bar) - + (idx == ndev->b2b_idx ? ndev->b2b_off : 0); + + return 0; +} + static int intel_ntb_db_is_unsafe(struct ntb_dev *ntb) { return ndev_ignore_unsafe(ntb_ndev(ntb), NTB_UNSAFE_DB); @@ -1902,7 +1945,7 @@ static int intel_ntb3_link_enable(struct ntb_dev *ntb, return 0; } -static int intel_ntb3_mw_set_trans(struct ntb_dev *ntb, int idx, +static int intel_ntb3_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx, dma_addr_t addr, resource_size_t size) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1912,6 +1955,9 @@ static int intel_ntb3_mw_set_trans(struct ntb_dev *ntb, int idx, u64 base, limit, reg_val; int bar; + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + if (idx >= ndev->b2b_idx && !ndev->b2b_off) idx += 1; @@ -2906,8 +2952,10 @@ static const struct intel_ntb_xlat_reg skx_sec_xlat = { /* operations for primary side of local ntb */ static const struct ntb_dev_ops intel_ntb_ops = { .mw_count = intel_ntb_mw_count, - .mw_get_range = intel_ntb_mw_get_range, + .mw_get_align = intel_ntb_mw_get_align, .mw_set_trans = intel_ntb_mw_set_trans, + .peer_mw_count = intel_ntb_peer_mw_count, + .peer_mw_get_addr = intel_ntb_peer_mw_get_addr, .link_is_up = intel_ntb_link_is_up, .link_enable = intel_ntb_link_enable, .link_disable = intel_ntb_link_disable, @@ -2932,8 +2980,10 @@ static const struct ntb_dev_ops intel_ntb_ops = { static const struct ntb_dev_ops intel_ntb3_ops = { .mw_count = intel_ntb_mw_count, - .mw_get_range = intel_ntb_mw_get_range, + .mw_get_align = intel_ntb_mw_get_align, .mw_set_trans = intel_ntb3_mw_set_trans, + .peer_mw_count = intel_ntb_peer_mw_count, + .peer_mw_get_addr = intel_ntb_peer_mw_get_addr, .link_is_up = intel_ntb_link_is_up, .link_enable = intel_ntb3_link_enable, .link_disable = intel_ntb_link_disable, diff --git a/drivers/ntb/ntb.c b/drivers/ntb/ntb.c index 1e92e52813aa..2551bb2ff4a4 100644 --- a/drivers/ntb/ntb.c +++ b/drivers/ntb/ntb.c @@ -5,6 +5,7 @@ * GPL LICENSE SUMMARY * * Copyright (C) 2015 EMC Corporation. All Rights Reserved. + * Copyright (C) 2016 T-Platforms. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as @@ -18,6 +19,7 @@ * BSD LICENSE * * Copyright (C) 2015 EMC Corporation. All Rights Reserved. + * Copyright (C) 2016 T-Platforms. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index cc6ae35109b1..771b469cebf0 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -673,7 +673,7 @@ static void ntb_free_mw(struct ntb_transport_ctx *nt, int num_mw) if (!mw->virt_addr) return; - ntb_mw_clear_trans(nt->ndev, num_mw); + ntb_mw_clear_trans(nt->ndev, PIDX, num_mw); dma_free_coherent(&pdev->dev, mw->buff_size, mw->virt_addr, mw->dma_addr); mw->xlat_size = 0; @@ -730,7 +730,8 @@ static int ntb_set_mw(struct ntb_transport_ctx *nt, int num_mw, } /* Notify HW the memory location of the receive buffer */ - rc = ntb_mw_set_trans(nt->ndev, num_mw, mw->dma_addr, mw->xlat_size); + rc = ntb_mw_set_trans(nt->ndev, PIDX, num_mw, mw->dma_addr, + mw->xlat_size); if (rc) { dev_err(&pdev->dev, "Unable to set mw%d translation", num_mw); ntb_free_mw(nt, num_mw); @@ -1058,7 +1059,12 @@ static int ntb_transport_probe(struct ntb_client *self, struct ntb_dev *ndev) int node; int rc, i; - mw_count = ntb_mw_count(ndev); + mw_count = ntb_mw_count(ndev, PIDX); + + if (!ndev->ops->mw_set_trans) { + dev_err(&ndev->dev, "Inbound MW based NTB API is required\n"); + return -EINVAL; + } if (ntb_db_is_unsafe(ndev)) dev_dbg(&ndev->dev, @@ -1100,8 +1106,13 @@ static int ntb_transport_probe(struct ntb_client *self, struct ntb_dev *ndev) for (i = 0; i < mw_count; i++) { mw = &nt->mw_vec[i]; - rc = ntb_mw_get_range(ndev, i, &mw->phys_addr, &mw->phys_size, - &mw->xlat_align, &mw->xlat_align_size); + rc = ntb_mw_get_align(ndev, PIDX, i, &mw->xlat_align, + &mw->xlat_align_size, NULL); + if (rc) + goto err1; + + rc = ntb_peer_mw_get_addr(ndev, i, &mw->phys_addr, + &mw->phys_size); if (rc) goto err1; diff --git a/drivers/ntb/test/ntb_perf.c b/drivers/ntb/test/ntb_perf.c index e52cc4eace90..7f89cceaf350 100644 --- a/drivers/ntb/test/ntb_perf.c +++ b/drivers/ntb/test/ntb_perf.c @@ -455,7 +455,7 @@ static void perf_free_mw(struct perf_ctx *perf) if (!mw->virt_addr) return; - ntb_mw_clear_trans(perf->ntb, 0); + ntb_mw_clear_trans(perf->ntb, PIDX, 0); dma_free_coherent(&pdev->dev, mw->buf_size, mw->virt_addr, mw->dma_addr); mw->xlat_size = 0; @@ -491,7 +491,7 @@ static int perf_set_mw(struct perf_ctx *perf, resource_size_t size) mw->buf_size = 0; } - rc = ntb_mw_set_trans(perf->ntb, 0, mw->dma_addr, mw->xlat_size); + rc = ntb_mw_set_trans(perf->ntb, PIDX, 0, mw->dma_addr, mw->xlat_size); if (rc) { dev_err(&perf->ntb->dev, "Unable to set mw0 translation\n"); perf_free_mw(perf); @@ -562,8 +562,12 @@ static int perf_setup_mw(struct ntb_dev *ntb, struct perf_ctx *perf) mw = &perf->mw; - rc = ntb_mw_get_range(ntb, 0, &mw->phys_addr, &mw->phys_size, - &mw->xlat_align, &mw->xlat_align_size); + rc = ntb_mw_get_align(ntb, PIDX, 0, &mw->xlat_align, + &mw->xlat_align_size, NULL); + if (rc) + return rc; + + rc = ntb_peer_mw_get_addr(ntb, 0, &mw->phys_addr, &mw->phys_size); if (rc) return rc; @@ -767,6 +771,11 @@ static int perf_probe(struct ntb_client *client, struct ntb_dev *ntb) return -EIO; } + if (!ntb->ops->mw_set_trans) { + dev_err(&ntb->dev, "Need inbound MW based NTB API\n"); + return -EINVAL; + } + if (ntb_peer_port_count(ntb) != NTB_DEF_PEER_CNT) dev_warn(&ntb->dev, "Multi-port NTB devices unsupported\n"); diff --git a/drivers/ntb/test/ntb_tool.c b/drivers/ntb/test/ntb_tool.c index 690862d90411..cb692473d457 100644 --- a/drivers/ntb/test/ntb_tool.c +++ b/drivers/ntb/test/ntb_tool.c @@ -119,7 +119,8 @@ MODULE_VERSION(DRIVER_VERSION); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESCRIPTION); -#define MAX_MWS 16 +/* It is rare to have hadrware with greater than six MWs */ +#define MAX_MWS 6 /* Only two-ports devices are supported */ #define PIDX NTB_DEF_PEER_IDX @@ -670,28 +671,27 @@ static int tool_setup_mw(struct tool_ctx *tc, int idx, size_t req_size) { int rc; struct tool_mw *mw = &tc->mws[idx]; - phys_addr_t base; - resource_size_t size, align, align_size; + resource_size_t size, align_addr, align_size; char buf[16]; if (mw->peer) return 0; - rc = ntb_mw_get_range(tc->ntb, idx, &base, &size, &align, - &align_size); + rc = ntb_mw_get_align(tc->ntb, PIDX, idx, &align_addr, + &align_size, &size); if (rc) return rc; mw->size = min_t(resource_size_t, req_size, size); - mw->size = round_up(mw->size, align); + mw->size = round_up(mw->size, align_addr); mw->size = round_up(mw->size, align_size); mw->peer = dma_alloc_coherent(&tc->ntb->pdev->dev, mw->size, &mw->peer_dma, GFP_KERNEL); - if (!mw->peer) + if (!mw->peer || !IS_ALIGNED(mw->peer_dma, align_addr)) return -ENOMEM; - rc = ntb_mw_set_trans(tc->ntb, idx, mw->peer_dma, mw->size); + rc = ntb_mw_set_trans(tc->ntb, PIDX, idx, mw->peer_dma, mw->size); if (rc) goto err_free_dma; @@ -718,7 +718,7 @@ static void tool_free_mw(struct tool_ctx *tc, int idx) struct tool_mw *mw = &tc->mws[idx]; if (mw->peer) { - ntb_mw_clear_trans(tc->ntb, idx); + ntb_mw_clear_trans(tc->ntb, PIDX, idx); dma_free_coherent(&tc->ntb->pdev->dev, mw->size, mw->peer, mw->peer_dma); @@ -744,8 +744,9 @@ static ssize_t tool_peer_mw_trans_read(struct file *filep, phys_addr_t base; resource_size_t mw_size; - resource_size_t align; + resource_size_t align_addr; resource_size_t align_size; + resource_size_t max_size; buf_size = min_t(size_t, size, 512); @@ -753,8 +754,9 @@ static ssize_t tool_peer_mw_trans_read(struct file *filep, if (!buf) return -ENOMEM; - ntb_mw_get_range(mw->tc->ntb, mw->idx, - &base, &mw_size, &align, &align_size); + ntb_mw_get_align(mw->tc->ntb, PIDX, mw->idx, + &align_addr, &align_size, &max_size); + ntb_peer_mw_get_addr(mw->tc->ntb, mw->idx, &base, &mw_size); off += scnprintf(buf + off, buf_size - off, "Peer MW %d Information:\n", mw->idx); @@ -769,13 +771,17 @@ static ssize_t tool_peer_mw_trans_read(struct file *filep, off += scnprintf(buf + off, buf_size - off, "Alignment \t%lld\n", - (unsigned long long)align); + (unsigned long long)align_addr); off += scnprintf(buf + off, buf_size - off, "Size Alignment \t%lld\n", (unsigned long long)align_size); off += scnprintf(buf + off, buf_size - off, + "Size Max \t%lld\n", + (unsigned long long)max_size); + + off += scnprintf(buf + off, buf_size - off, "Ready \t%c\n", (mw->peer) ? 'Y' : 'N'); @@ -829,8 +835,7 @@ static int tool_init_mw(struct tool_ctx *tc, int idx) phys_addr_t base; int rc; - rc = ntb_mw_get_range(tc->ntb, idx, &base, &mw->win_size, - NULL, NULL); + rc = ntb_peer_mw_get_addr(tc->ntb, idx, &base, &mw->win_size); if (rc) return rc; @@ -915,6 +920,12 @@ static int tool_probe(struct ntb_client *self, struct ntb_dev *ntb) int rc; int i; + if (!ntb->ops->mw_set_trans) { + dev_dbg(&ntb->dev, "need inbound MW based NTB API\n"); + rc = -EINVAL; + goto err_tc; + } + if (ntb_db_is_unsafe(ntb)) dev_dbg(&ntb->dev, "doorbell is unsafe\n"); @@ -933,7 +944,7 @@ static int tool_probe(struct ntb_client *self, struct ntb_dev *ntb) tc->ntb = ntb; init_waitqueue_head(&tc->link_wq); - tc->mw_count = min(ntb_mw_count(tc->ntb), MAX_MWS); + tc->mw_count = min(ntb_mw_count(tc->ntb, PIDX), MAX_MWS); for (i = 0; i < tc->mw_count; i++) { rc = tool_init_mw(tc, i); if (rc) |