Commit 38bd9d9f authored by David S. Miller's avatar David S. Miller

Merge branch 'ionic-txrx-updates'

Shannon Nelson says:

====================
ionic txrx updates

These are a few patches to do some cleanup in the packet
handling and give us more flexibility in tuning performance
by allowing us to put Tx handling on separate interrupts
when it makes sense for particular traffic loads.

v3: simplified queue count change logging, removed unnecessary
    check for no count change
v2: dropped the original patch 2 for ringsize change
    changed the separated tx/rx interrupts to use ethtool -L
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 622e32b7 fe8c30b5
......@@ -403,8 +403,7 @@ static int ionic_get_coalesce(struct net_device *netdev,
{
struct ionic_lif *lif = netdev_priv(netdev);
/* Tx uses Rx interrupt */
coalesce->tx_coalesce_usecs = lif->rx_coalesce_usecs;
coalesce->tx_coalesce_usecs = lif->tx_coalesce_usecs;
coalesce->rx_coalesce_usecs = lif->rx_coalesce_usecs;
return 0;
......@@ -417,7 +416,8 @@ static int ionic_set_coalesce(struct net_device *netdev,
struct ionic_identity *ident;
struct ionic_qcq *qcq;
unsigned int i;
u32 coal;
u32 rx_coal;
u32 tx_coal;
ident = &lif->ionic->ident;
if (ident->dev.intr_coal_div == 0) {
......@@ -426,26 +426,31 @@ static int ionic_set_coalesce(struct net_device *netdev,
return -EIO;
}
/* Tx uses Rx interrupt, so only change Rx */
if (coalesce->tx_coalesce_usecs != lif->rx_coalesce_usecs) {
/* Tx normally shares Rx interrupt, so only change Rx */
if (!test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state) &&
coalesce->tx_coalesce_usecs != lif->rx_coalesce_usecs) {
netdev_warn(netdev, "only the rx-usecs can be changed\n");
return -EINVAL;
}
/* Convert the usec request to a HW useable value. If they asked
/* Convert the usec request to a HW usable value. If they asked
* for non-zero and it resolved to zero, bump it up
*/
coal = ionic_coal_usec_to_hw(lif->ionic, coalesce->rx_coalesce_usecs);
if (!coal && coalesce->rx_coalesce_usecs)
coal = 1;
if (coal > IONIC_INTR_CTRL_COAL_MAX)
rx_coal = ionic_coal_usec_to_hw(lif->ionic, coalesce->rx_coalesce_usecs);
if (!rx_coal && coalesce->rx_coalesce_usecs)
rx_coal = 1;
tx_coal = ionic_coal_usec_to_hw(lif->ionic, coalesce->tx_coalesce_usecs);
if (!tx_coal && coalesce->tx_coalesce_usecs)
tx_coal = 1;
if (rx_coal > IONIC_INTR_CTRL_COAL_MAX ||
tx_coal > IONIC_INTR_CTRL_COAL_MAX)
return -ERANGE;
/* Save the new value */
/* Save the new values */
lif->rx_coalesce_usecs = coalesce->rx_coalesce_usecs;
if (coal != lif->rx_coalesce_hw) {
lif->rx_coalesce_hw = coal;
if (rx_coal != lif->rx_coalesce_hw) {
lif->rx_coalesce_hw = rx_coal;
if (test_bit(IONIC_LIF_F_UP, lif->state)) {
for (i = 0; i < lif->nxqs; i++) {
......@@ -457,6 +462,23 @@ static int ionic_set_coalesce(struct net_device *netdev,
}
}
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
lif->tx_coalesce_usecs = coalesce->tx_coalesce_usecs;
else
lif->tx_coalesce_usecs = coalesce->rx_coalesce_usecs;
if (tx_coal != lif->tx_coalesce_hw) {
lif->tx_coalesce_hw = tx_coal;
if (test_bit(IONIC_LIF_F_UP, lif->state)) {
for (i = 0; i < lif->nxqs; i++) {
qcq = lif->txqcqs[i].qcq;
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
qcq->intr.index,
lif->tx_coalesce_hw);
}
}
}
return 0;
}
......@@ -510,29 +532,63 @@ static void ionic_get_channels(struct net_device *netdev,
/* report maximum channels */
ch->max_combined = lif->ionic->ntxqs_per_lif;
ch->max_rx = lif->ionic->ntxqs_per_lif / 2;
ch->max_tx = lif->ionic->ntxqs_per_lif / 2;
/* report current channels */
ch->combined_count = lif->nxqs;
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state)) {
ch->rx_count = lif->nxqs;
ch->tx_count = lif->nxqs;
} else {
ch->combined_count = lif->nxqs;
}
}
static void ionic_set_queuecount(struct ionic_lif *lif, void *arg)
{
struct ethtool_channels *ch = arg;
lif->nxqs = ch->combined_count;
if (ch->combined_count) {
lif->nxqs = ch->combined_count;
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state)) {
clear_bit(IONIC_LIF_F_SPLIT_INTR, lif->state);
lif->tx_coalesce_usecs = lif->rx_coalesce_usecs;
lif->tx_coalesce_hw = lif->rx_coalesce_hw;
netdev_info(lif->netdev, "Sharing queue interrupts\n");
}
} else {
lif->nxqs = ch->rx_count;
if (!test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state)) {
set_bit(IONIC_LIF_F_SPLIT_INTR, lif->state);
netdev_info(lif->netdev, "Splitting queue interrupts\n");
}
}
}
static int ionic_set_channels(struct net_device *netdev,
struct ethtool_channels *ch)
{
struct ionic_lif *lif = netdev_priv(netdev);
int new_cnt;
if (!ch->combined_count || ch->other_count ||
ch->rx_count || ch->tx_count)
if (ch->rx_count != ch->tx_count) {
netdev_info(netdev, "The rx and tx count must be equal\n");
return -EINVAL;
}
if (ch->combined_count == lif->nxqs)
return 0;
if (ch->combined_count && ch->rx_count) {
netdev_info(netdev, "Use either combined_count or rx/tx_count, not both\n");
return -EINVAL;
}
if (ch->combined_count)
new_cnt = ch->combined_count;
else
new_cnt = ch->rx_count;
if (lif->nxqs != new_cnt)
netdev_info(netdev, "Changing queue count from %d to %d\n",
lif->nxqs, new_cnt);
return ionic_reset_queues(lif, ionic_set_queuecount, ch);
}
......
......@@ -616,7 +616,6 @@ static int ionic_lif_txq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
.index = cpu_to_le32(q->index),
.flags = cpu_to_le16(IONIC_QINIT_F_IRQ |
IONIC_QINIT_F_SG),
.intr_index = cpu_to_le16(lif->rxqcqs[q->index].qcq->intr.index),
.pid = cpu_to_le16(q->pid),
.ring_size = ilog2(q->num_descs),
.ring_base = cpu_to_le64(q->base_pa),
......@@ -624,14 +623,22 @@ static int ionic_lif_txq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
.sg_ring_base = cpu_to_le64(q->sg_base_pa),
},
};
unsigned int intr_index;
int err;
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
intr_index = qcq->intr.index;
else
intr_index = lif->rxqcqs[q->index].qcq->intr.index;
ctx.cmd.q_init.intr_index = cpu_to_le16(intr_index);
dev_dbg(dev, "txq_init.pid %d\n", ctx.cmd.q_init.pid);
dev_dbg(dev, "txq_init.index %d\n", ctx.cmd.q_init.index);
dev_dbg(dev, "txq_init.ring_base 0x%llx\n", ctx.cmd.q_init.ring_base);
dev_dbg(dev, "txq_init.ring_size %d\n", ctx.cmd.q_init.ring_size);
dev_dbg(dev, "txq_init.flags 0x%x\n", ctx.cmd.q_init.flags);
dev_dbg(dev, "txq_init.ver %d\n", ctx.cmd.q_init.ver);
dev_dbg(dev, "txq_init.intr_index %d\n", ctx.cmd.q_init.intr_index);
q->tail = q->info;
q->head = q->tail;
......@@ -648,6 +655,10 @@ static int ionic_lif_txq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
dev_dbg(dev, "txq->hw_type %d\n", q->hw_type);
dev_dbg(dev, "txq->hw_index %d\n", q->hw_index);
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
netif_napi_add(lif->netdev, &qcq->napi, ionic_tx_napi,
NAPI_POLL_WEIGHT);
qcq->flags |= IONIC_QCQ_F_INITED;
return 0;
......@@ -684,6 +695,7 @@ static int ionic_lif_rxq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
dev_dbg(dev, "rxq_init.ring_size %d\n", ctx.cmd.q_init.ring_size);
dev_dbg(dev, "rxq_init.flags 0x%x\n", ctx.cmd.q_init.flags);
dev_dbg(dev, "rxq_init.ver %d\n", ctx.cmd.q_init.ver);
dev_dbg(dev, "rxq_init.intr_index %d\n", ctx.cmd.q_init.intr_index);
q->tail = q->info;
q->head = q->tail;
......@@ -700,8 +712,12 @@ static int ionic_lif_rxq_init(struct ionic_lif *lif, struct ionic_qcq *qcq)
dev_dbg(dev, "rxq->hw_type %d\n", q->hw_type);
dev_dbg(dev, "rxq->hw_index %d\n", q->hw_index);
netif_napi_add(lif->netdev, &qcq->napi, ionic_rx_napi,
NAPI_POLL_WEIGHT);
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
netif_napi_add(lif->netdev, &qcq->napi, ionic_rx_napi,
NAPI_POLL_WEIGHT);
else
netif_napi_add(lif->netdev, &qcq->napi, ionic_txrx_napi,
NAPI_POLL_WEIGHT);
qcq->flags |= IONIC_QCQ_F_INITED;
......@@ -1537,6 +1553,8 @@ static int ionic_txrx_alloc(struct ionic_lif *lif)
sg_desc_sz = sizeof(struct ionic_txq_sg_desc);
flags = IONIC_QCQ_F_TX_STATS | IONIC_QCQ_F_SG;
if (test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
flags |= IONIC_QCQ_F_INTR;
for (i = 0; i < lif->nxqs; i++) {
err = ionic_qcq_alloc(lif, IONIC_QTYPE_TXQ, i, "tx", flags,
lif->ntxq_descs,
......@@ -1547,6 +1565,11 @@ static int ionic_txrx_alloc(struct ionic_lif *lif)
if (err)
goto err_out;
if (flags & IONIC_QCQ_F_INTR)
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
lif->txqcqs[i].qcq->intr.index,
lif->tx_coalesce_hw);
lif->txqcqs[i].qcq->stats = lif->txqcqs[i].stats;
ionic_debugfs_add_qcq(lif, lif->txqcqs[i].qcq);
}
......@@ -1562,13 +1585,15 @@ static int ionic_txrx_alloc(struct ionic_lif *lif)
if (err)
goto err_out;
lif->rxqcqs[i].qcq->stats = lif->rxqcqs[i].stats;
ionic_intr_coal_init(lif->ionic->idev.intr_ctrl,
lif->rxqcqs[i].qcq->intr.index,
lif->rx_coalesce_hw);
ionic_link_qcq_interrupts(lif->rxqcqs[i].qcq,
lif->txqcqs[i].qcq);
if (!test_bit(IONIC_LIF_F_SPLIT_INTR, lif->state))
ionic_link_qcq_interrupts(lif->rxqcqs[i].qcq,
lif->txqcqs[i].qcq);
lif->rxqcqs[i].qcq->stats = lif->rxqcqs[i].stats;
ionic_debugfs_add_qcq(lif, lif->rxqcqs[i].qcq);
}
......@@ -2065,11 +2090,14 @@ static struct ionic_lif *ionic_lif_alloc(struct ionic *ionic, unsigned int index
lif->index = index;
lif->ntxq_descs = IONIC_DEF_TXRX_DESC;
lif->nrxq_descs = IONIC_DEF_TXRX_DESC;
lif->tx_budget = IONIC_TX_BUDGET_DEFAULT;
/* Convert the default coalesce value to actual hw resolution */
lif->rx_coalesce_usecs = IONIC_ITR_COAL_USEC_DEFAULT;
lif->rx_coalesce_hw = ionic_coal_usec_to_hw(lif->ionic,
lif->rx_coalesce_usecs);
lif->tx_coalesce_usecs = lif->rx_coalesce_usecs;
lif->tx_coalesce_hw = lif->rx_coalesce_hw;
snprintf(lif->name, sizeof(lif->name), "lif%u", index);
......
......@@ -13,6 +13,7 @@
#define IONIC_MAX_NUM_NAPI_CNTR (NAPI_POLL_WEIGHT + 1)
#define IONIC_MAX_NUM_SG_CNTR (IONIC_TX_MAX_SG_ELEMS + 1)
#define IONIC_RX_COPYBREAK_DEFAULT 256
#define IONIC_TX_BUDGET_DEFAULT 256
struct ionic_tx_stats {
u64 dma_map_err;
......@@ -136,6 +137,7 @@ enum ionic_lif_state_flags {
IONIC_LIF_F_UP,
IONIC_LIF_F_LINK_CHECK_REQUESTED,
IONIC_LIF_F_FW_RESET,
IONIC_LIF_F_SPLIT_INTR,
/* leave this as last */
IONIC_LIF_F_STATE_SIZE
......@@ -176,6 +178,7 @@ struct ionic_lif {
unsigned int ntxq_descs;
unsigned int nrxq_descs;
u32 rx_copybreak;
u32 tx_budget;
unsigned int rx_mode;
u64 hw_features;
bool mc_overflow;
......@@ -203,6 +206,8 @@ struct ionic_lif {
struct dentry *dentry;
u32 rx_coalesce_usecs; /* what the user asked for */
u32 rx_coalesce_hw; /* what the hw is using */
u32 tx_coalesce_usecs; /* what the user asked for */
u32 tx_coalesce_hw; /* what the hw is using */
struct work_struct tx_timeout_work;
};
......
......@@ -15,6 +15,10 @@ static void ionic_rx_clean(struct ionic_queue *q,
struct ionic_cq_info *cq_info,
void *cb_arg);
static bool ionic_rx_service(struct ionic_cq *cq, struct ionic_cq_info *cq_info);
static bool ionic_tx_service(struct ionic_cq *cq, struct ionic_cq_info *cq_info);
static inline void ionic_txq_post(struct ionic_queue *q, bool ring_dbell,
ionic_desc_cb cb_func, void *cb_arg)
{
......@@ -249,29 +253,13 @@ static bool ionic_rx_service(struct ionic_cq *cq, struct ionic_cq_info *cq_info)
return true;
}
static u32 ionic_rx_walk_cq(struct ionic_cq *rxcq, u32 limit)
{
u32 work_done = 0;
while (ionic_rx_service(rxcq, rxcq->tail)) {
if (rxcq->tail->last)
rxcq->done_color = !rxcq->done_color;
rxcq->tail = rxcq->tail->next;
DEBUG_STATS_CQE_CNT(rxcq);
if (++work_done >= limit)
break;
}
return work_done;
}
void ionic_rx_flush(struct ionic_cq *cq)
{
struct ionic_dev *idev = &cq->lif->ionic->idev;
u32 work_done;
work_done = ionic_rx_walk_cq(cq, cq->num_descs);
work_done = ionic_cq_service(cq, cq->num_descs,
ionic_rx_service, NULL, NULL);
if (work_done)
ionic_intr_credits(idev->intr_ctrl, cq->bound_intr->index,
......@@ -331,9 +319,6 @@ static void ionic_rx_page_free(struct ionic_queue *q, struct page *page,
__free_page(page);
}
#define IONIC_RX_RING_DOORBELL_STRIDE ((1 << 5) - 1)
#define IONIC_RX_RING_HEAD_BUF_SZ 2048
void ionic_rx_fill(struct ionic_queue *q)
{
struct net_device *netdev = q->lif->netdev;
......@@ -345,7 +330,6 @@ void ionic_rx_fill(struct ionic_queue *q)
unsigned int remain_len;
unsigned int seg_len;
unsigned int nfrags;
bool ring_doorbell;
unsigned int i, j;
unsigned int len;
......@@ -360,9 +344,7 @@ void ionic_rx_fill(struct ionic_queue *q)
page_info = &desc_info->pages[0];
if (page_info->page) { /* recycle the buffer */
ring_doorbell = ((q->head->index + 1) &
IONIC_RX_RING_DOORBELL_STRIDE) == 0;
ionic_rxq_post(q, ring_doorbell, ionic_rx_clean, NULL);
ionic_rxq_post(q, false, ionic_rx_clean, NULL);
continue;
}
......@@ -401,10 +383,11 @@ void ionic_rx_fill(struct ionic_queue *q)
page_info++;
}
ring_doorbell = ((q->head->index + 1) &
IONIC_RX_RING_DOORBELL_STRIDE) == 0;
ionic_rxq_post(q, ring_doorbell, ionic_rx_clean, NULL);
ionic_rxq_post(q, false, ionic_rx_clean, NULL);
}
ionic_dbell_ring(q->lif->kern_dbpage, q->hw_type,
q->dbval | q->head->index);
}
static void ionic_rx_fill_cb(void *arg)
......@@ -436,7 +419,74 @@ void ionic_rx_empty(struct ionic_queue *q)
}
}
int ionic_tx_napi(struct napi_struct *napi, int budget)
{
struct ionic_qcq *qcq = napi_to_qcq(napi);
struct ionic_cq *cq = napi_to_cq(napi);
struct ionic_dev *idev;
struct ionic_lif *lif;
u32 work_done = 0;
u32 flags = 0;
lif = cq->bound_q->lif;
idev = &lif->ionic->idev;
work_done = ionic_cq_service(cq, budget,
ionic_tx_service, NULL, NULL);
if (work_done < budget && napi_complete_done(napi, work_done)) {
flags |= IONIC_INTR_CRED_UNMASK;
DEBUG_STATS_INTR_REARM(cq->bound_intr);
}
if (work_done || flags) {
flags |= IONIC_INTR_CRED_RESET_COALESCE;
ionic_intr_credits(idev->intr_ctrl,
cq->bound_intr->index,
work_done, flags);
}
DEBUG_STATS_NAPI_POLL(qcq, work_done);
return work_done;
}
int ionic_rx_napi(struct napi_struct *napi, int budget)
{
struct ionic_qcq *qcq = napi_to_qcq(napi);
struct ionic_cq *cq = napi_to_cq(napi);
struct ionic_dev *idev;
struct ionic_lif *lif;
u32 work_done = 0;
u32 flags = 0;
lif = cq->bound_q->lif;
idev = &lif->ionic->idev;
work_done = ionic_cq_service(cq, budget,
ionic_rx_service, NULL, NULL);
if (work_done)
ionic_rx_fill(cq->bound_q);
if (work_done < budget && napi_complete_done(napi, work_done)) {
flags |= IONIC_INTR_CRED_UNMASK;
DEBUG_STATS_INTR_REARM(cq->bound_intr);
}
if (work_done || flags) {
flags |= IONIC_INTR_CRED_RESET_COALESCE;
ionic_intr_credits(idev->intr_ctrl,
cq->bound_intr->index,
work_done, flags);
}
DEBUG_STATS_NAPI_POLL(qcq, work_done);
return work_done;
}
int ionic_txrx_napi(struct napi_struct *napi, int budget)
{
struct ionic_qcq *qcq = napi_to_qcq(napi);
struct ionic_cq *rxcq = napi_to_cq(napi);
......@@ -444,32 +494,42 @@ int ionic_rx_napi(struct napi_struct *napi, int budget)
struct ionic_dev *idev;
struct ionic_lif *lif;
struct ionic_cq *txcq;
u32 rx_work_done = 0;
u32 tx_work_done = 0;
u32 work_done = 0;
u32 flags = 0;
bool unmask;
lif = rxcq->bound_q->lif;
idev = &lif->ionic->idev;
txcq = &lif->txqcqs[qi].qcq->cq;
ionic_tx_flush(txcq);
tx_work_done = ionic_cq_service(txcq, lif->tx_budget,
ionic_tx_service, NULL, NULL);
work_done = ionic_rx_walk_cq(rxcq, budget);
if (work_done)
rx_work_done = ionic_cq_service(rxcq, budget,
ionic_rx_service, NULL, NULL);
if (rx_work_done)
ionic_rx_fill_cb(rxcq->bound_q);
if (work_done < budget && napi_complete_done(napi, work_done)) {
unmask = (rx_work_done < budget) && (tx_work_done < lif->tx_budget);
if (unmask && napi_complete_done(napi, rx_work_done)) {
flags |= IONIC_INTR_CRED_UNMASK;
DEBUG_STATS_INTR_REARM(rxcq->bound_intr);
work_done = rx_work_done;
} else {
work_done = budget;
}
if (work_done || flags) {
flags |= IONIC_INTR_CRED_RESET_COALESCE;
ionic_intr_credits(idev->intr_ctrl, rxcq->bound_intr->index,
work_done, flags);
tx_work_done + rx_work_done, flags);
}
DEBUG_STATS_NAPI_POLL(qcq, work_done);
DEBUG_STATS_NAPI_POLL(qcq, rx_work_done);
DEBUG_STATS_NAPI_POLL(qcq, tx_work_done);
return work_done;
}
......@@ -557,43 +617,39 @@ static void ionic_tx_clean(struct ionic_queue *q,
}
}
void ionic_tx_flush(struct ionic_cq *cq)
static bool ionic_tx_service(struct ionic_cq *cq, struct ionic_cq_info *cq_info)
{
struct ionic_txq_comp *comp = cq->tail->cq_desc;
struct ionic_dev *idev = &cq->lif->ionic->idev;
struct ionic_txq_comp *comp = cq_info->cq_desc;
struct ionic_queue *q = cq->bound_q;
struct ionic_desc_info *desc_info;
unsigned int work_done = 0;
/* walk the completed cq entries */
while (work_done < cq->num_descs &&
color_match(comp->color, cq->done_color)) {
/* clean the related q entries, there could be
* several q entries completed for each cq completion
*/
do {
desc_info = q->tail;
q->tail = desc_info->next;
ionic_tx_clean(q, desc_info, cq->tail,
desc_info->cb_arg);
desc_info->cb = NULL;
desc_info->cb_arg = NULL;
} while (desc_info->index != le16_to_cpu(comp->comp_index));
if (cq->tail->last)
cq->done_color = !cq->done_color;
cq->tail = cq->tail->next;
comp = cq->tail->cq_desc;
DEBUG_STATS_CQE_CNT(cq);
work_done++;
}
if (!color_match(comp->color, cq->done_color))
return false;
/* clean the related q entries, there could be
* several q entries completed for each cq completion
*/
do {
desc_info = q->tail;
q->tail = desc_info->next;
ionic_tx_clean(q, desc_info, cq->tail, desc_info->cb_arg);
desc_info->cb = NULL;
desc_info->cb_arg = NULL;
} while (desc_info->index != le16_to_cpu(comp->comp_index));
return true;
}
void ionic_tx_flush(struct ionic_cq *cq)
{
struct ionic_dev *idev = &cq->lif->ionic->idev;
u32 work_done;
work_done = ionic_cq_service(cq, cq->num_descs,
ionic_tx_service, NULL, NULL);
if (work_done)
ionic_intr_credits(idev->intr_ctrl, cq->bound_intr->index,
work_done, 0);
work_done, IONIC_INTR_CRED_RESET_COALESCE);
}
void ionic_tx_empty(struct ionic_queue *q)
......
......@@ -11,6 +11,8 @@ void ionic_rx_fill(struct ionic_queue *q);
void ionic_rx_empty(struct ionic_queue *q);
void ionic_tx_empty(struct ionic_queue *q);
int ionic_rx_napi(struct napi_struct *napi, int budget);
int ionic_tx_napi(struct napi_struct *napi, int budget);
int ionic_txrx_napi(struct napi_struct *napi, int budget);
netdev_tx_t ionic_start_xmit(struct sk_buff *skb, struct net_device *netdev);
#endif /* _IONIC_TXRX_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment