/* * Reserve 1/16 of TmFifo space, so console messages are not starved by * the networking traffic.
*/ #define MLXBF_TMFIFO_RESERVE_RATIO 16
/* Message with data needs at least two words (for header & data). */ #define MLXBF_TMFIFO_DATA_MIN_WORDS 2
/* Tx timeout in milliseconds. */ #define TMFIFO_TX_TIMEOUT 2000
/* ACPI UID for BlueField-3. */ #define TMFIFO_BF3_UID 1
struct mlxbf_tmfifo;
/** * struct mlxbf_tmfifo_vring - Structure of the TmFifo virtual ring * @va: virtual address of the ring * @dma: dma address of the ring * @vq: pointer to the virtio virtqueue * @desc: current descriptor of the pending packet * @desc_head: head descriptor of the pending packet * @drop_desc: dummy desc for packet dropping * @cur_len: processed length of the current descriptor * @rem_len: remaining length of the pending packet * @rem_padding: remaining bytes to send as paddings * @pkt_len: total length of the pending packet * @next_avail: next avail descriptor id * @num: vring size (number of descriptors) * @align: vring alignment size * @index: vring index * @vdev_id: vring virtio id (VIRTIO_ID_xxx) * @tx_timeout: expire time of last tx packet * @fifo: pointer to the tmfifo structure
*/ struct mlxbf_tmfifo_vring { void *va;
dma_addr_t dma; struct virtqueue *vq; struct vring_desc *desc; struct vring_desc *desc_head; struct vring_desc drop_desc; int cur_len; int rem_len; int rem_padding;
u32 pkt_len;
u16 next_avail; int num; int align; int index; int vdev_id; unsignedlong tx_timeout; struct mlxbf_tmfifo *fifo;
};
/* Check whether vring is in drop mode. */ #define IS_VRING_DROP(_r) ({ \
typeof(_r) (r) = (_r); \
r->desc_head == &r->drop_desc; })
/* A stub length to drop maximum length packet. */ #define VRING_DROP_DESC_MAX_LEN GENMASK(15, 0)
/** * struct mlxbf_tmfifo_vdev - Structure of the TmFifo virtual device * @vdev: virtio device, in which the vdev.id.device field has the * VIRTIO_ID_xxx id to distinguish the virtual device. * @status: status of the device * @features: supported features of the device * @vrings: array of tmfifo vrings of this device * @config: non-anonymous union for cons and net * @config.cons: virtual console config - * select if vdev.id.device is VIRTIO_ID_CONSOLE * @config.net: virtual network config - * select if vdev.id.device is VIRTIO_ID_NET * @tx_buf: tx buffer used to buffer data before writing into the FIFO
*/ struct mlxbf_tmfifo_vdev { struct virtio_device vdev;
u8 status;
u64 features; struct mlxbf_tmfifo_vring vrings[MLXBF_TMFIFO_VRING_MAX]; union { struct virtio_console_config cons; struct virtio_net_config net;
} config; struct circ_buf tx_buf;
};
/** * struct mlxbf_tmfifo_irq_info - Structure of the interrupt information * @fifo: pointer to the tmfifo structure * @irq: interrupt number * @index: index into the interrupt array
*/ struct mlxbf_tmfifo_irq_info { struct mlxbf_tmfifo *fifo; int irq; int index;
};
/** * struct mlxbf_tmfifo_io - Structure of the TmFifo IO resource (for both rx & tx) * @ctl: control register offset (TMFIFO_RX_CTL / TMFIFO_TX_CTL) * @sts: status register offset (TMFIFO_RX_STS / TMFIFO_TX_STS) * @data: data register offset (TMFIFO_RX_DATA / TMFIFO_TX_DATA)
*/ struct mlxbf_tmfifo_io { void __iomem *ctl; void __iomem *sts; void __iomem *data;
};
/** * struct mlxbf_tmfifo - Structure of the TmFifo * @vdev: array of the virtual devices running over the TmFifo * @lock: lock to protect the TmFifo access * @res0: mapped resource block 0 * @res1: mapped resource block 1 * @rx: rx io resource * @tx: tx io resource * @rx_fifo_size: number of entries of the Rx FIFO * @tx_fifo_size: number of entries of the Tx FIFO * @pend_events: pending bits for deferred events * @irq_info: interrupt information * @work: work struct for deferred process * @timer: background timer * @vring: Tx/Rx ring * @spin_lock: Tx/Rx spin lock * @is_ready: ready flag
*/ struct mlxbf_tmfifo { struct mlxbf_tmfifo_vdev *vdev[MLXBF_TMFIFO_VDEV_MAX]; struct mutex lock; /* TmFifo lock */ void __iomem *res0; void __iomem *res1; struct mlxbf_tmfifo_io rx; struct mlxbf_tmfifo_io tx; int rx_fifo_size; int tx_fifo_size; unsignedlong pend_events; struct mlxbf_tmfifo_irq_info irq_info[MLXBF_TM_MAX_IRQ]; struct work_struct work; struct timer_list timer; struct mlxbf_tmfifo_vring *vring[2];
spinlock_t spin_lock[2]; /* spin lock */ bool is_ready;
};
/** * struct mlxbf_tmfifo_msg_hdr - Structure of the TmFifo message header * @type: message type * @len: payload length in network byte order. Messages sent into the FIFO * will be read by the other side as data stream in the same byte order. * The length needs to be encoded into network order so both sides * could understand it.
*/ struct mlxbf_tmfifo_msg_hdr {
u8 type;
__be16 len; /* private: */
u8 unused[5];
} __packed __aligned(sizeof(u64));
/* * Default MAC. * This MAC address will be read from EFI persistent variable if configured. * It can also be reconfigured with standard Linux tools.
*/ static u8 mlxbf_tmfifo_net_default_mac[ETH_ALEN] = {
0x00, 0x1A, 0xCA, 0xFF, 0xFF, 0x01
};
/* EFI variable name of the MAC address. */ static efi_char16_t mlxbf_tmfifo_efi_name[] = L"RshimMacAddr";
/* * Virtio could poll and check the 'idx' to decide whether the desc is * done or not. Add a memory barrier here to make sure the update above * completes before updating the idx.
*/
virtio_mb(false);
vr->used->idx = cpu_to_virtio16(vdev, vr_idx + 1);
}
/* Get the total length of the descriptor chain. */ static u32 mlxbf_tmfifo_get_pkt_len(struct mlxbf_tmfifo_vring *vring, struct vring_desc *desc)
{ conststruct vring *vr = virtqueue_get_vring(vring->vq); struct virtio_device *vdev = vring->vq->vdev;
u32 len = 0, idx;
while (desc) {
len += virtio32_to_cpu(vdev, desc->len); if (!(virtio16_to_cpu(vdev, desc->flags) & VRING_DESC_F_NEXT)) break;
idx = virtio16_to_cpu(vdev, desc->next);
desc = &vr->desc[idx];
}
/* Get and initialize the next packet. */ staticstruct vring_desc *
mlxbf_tmfifo_get_next_pkt(struct mlxbf_tmfifo_vring *vring, bool is_rx)
{ struct vring_desc *desc;
/* Get the number of available words in the TmFifo for sending. */ staticint mlxbf_tmfifo_get_tx_avail(struct mlxbf_tmfifo *fifo, int vdev_id)
{ int tx_reserve;
u32 count;
u64 sts;
/* Reserve some room in FIFO for console messages. */ if (vdev_id == VIRTIO_ID_NET)
tx_reserve = fifo->tx_fifo_size / MLXBF_TMFIFO_RESERVE_RATIO; else
tx_reserve = 1;
/* Console Tx (move data from the output buffer into the TmFifo). */ staticvoid mlxbf_tmfifo_console_tx(struct mlxbf_tmfifo *fifo, int avail)
{ struct mlxbf_tmfifo_msg_hdr hdr; struct mlxbf_tmfifo_vdev *cons; unsignedlong flags; int size, seg; void *addr;
u64 data;
/* Return if not enough space available. */ if (avail < MLXBF_TMFIFO_DATA_MIN_WORDS) return;
cons = fifo->vdev[VIRTIO_ID_CONSOLE]; if (!cons || !cons->tx_buf.buf) return;
/* Return if no data to send. */
size = CIRC_CNT(cons->tx_buf.head, cons->tx_buf.tail,
MLXBF_TMFIFO_CON_TX_BUF_SIZE); if (size == 0) return;
/* Adjust the size to available space. */ if (size + sizeof(hdr) > avail * sizeof(u64))
size = avail * sizeof(u64) - sizeof(hdr);
/* Rx/Tx one word in the descriptor buffer. */ staticvoid mlxbf_tmfifo_rxtx_word(struct mlxbf_tmfifo_vring *vring, struct vring_desc *desc, bool is_rx, int len)
{ struct virtio_device *vdev = vring->vq->vdev; struct mlxbf_tmfifo *fifo = vring->fifo; void *addr;
u64 data;
/* Get the buffer address of this desc. */
addr = phys_to_virt(virtio64_to_cpu(vdev, desc->addr));
/* Read a word from FIFO for Rx. */ if (is_rx)
data = readq(fifo->rx.data);
if (vring->cur_len + sizeof(u64) <= len) { /* The whole word. */ if (is_rx) { if (!IS_VRING_DROP(vring))
memcpy(addr + vring->cur_len, &data, sizeof(u64));
} else {
memcpy(&data, addr + vring->cur_len, sizeof(u64));
}
vring->cur_len += sizeof(u64);
} else { /* Leftover bytes. */ if (is_rx) { if (!IS_VRING_DROP(vring))
memcpy(addr + vring->cur_len, &data,
len - vring->cur_len);
} else {
data = 0;
memcpy(&data, addr + vring->cur_len,
len - vring->cur_len);
}
vring->cur_len = len;
}
/* Write the word into FIFO for Tx. */ if (!is_rx)
writeq(data, fifo->tx.data);
}
/* * Rx/Tx packet header. * * In Rx case, the packet might be found to belong to a different vring since * the TmFifo is shared by different services. In such case, the 'vring_change' * flag is set.
*/ staticvoid mlxbf_tmfifo_rxtx_header(struct mlxbf_tmfifo_vring *vring, struct vring_desc **desc, bool is_rx, bool *vring_change)
{ struct mlxbf_tmfifo *fifo = vring->fifo; struct virtio_net_config *config; struct mlxbf_tmfifo_msg_hdr hdr; int vdev_id, hdr_len; bool drop_rx = false;
/* Read/Write packet header. */ if (is_rx) { /* Drain one word from the FIFO. */
*(u64 *)&hdr = readq(fifo->rx.data);
/* Skip the length 0 packets (keepalive). */ if (hdr.len == 0) return;
/* * Check whether the new packet still belongs to this vring. * If not, update the pkt_len of the new vring.
*/ if (vdev_id != vring->vdev_id) { struct mlxbf_tmfifo_vdev *tm_dev2 = fifo->vdev[vdev_id];
/* * Rx/Tx one descriptor. * * Return true to indicate more data available.
*/ staticbool mlxbf_tmfifo_rxtx_one_desc(struct mlxbf_tmfifo_vring *vring, bool is_rx, int *avail)
{ conststruct vring *vr = virtqueue_get_vring(vring->vq); struct mlxbf_tmfifo *fifo = vring->fifo; struct virtio_device *vdev; bool vring_change = false; struct vring_desc *desc; unsignedlong flags;
u32 len, idx;
vdev = &fifo->vdev[vring->vdev_id]->vdev;
/* Get the descriptor of the next packet. */ if (!vring->desc) {
desc = mlxbf_tmfifo_get_next_pkt(vring, is_rx); if (!desc) { /* Drop next Rx packet to avoid stuck. */ if (is_rx) {
desc = &vring->drop_desc;
vring->desc_head = desc;
vring->desc = desc;
} else { returnfalse;
}
}
} else {
desc = vring->desc;
}
/* Beginning of a packet. Start to Rx/Tx packet header. */ if (vring->pkt_len == 0) {
mlxbf_tmfifo_rxtx_header(vring, &desc, is_rx, &vring_change);
(*avail)--;
/* Return if new packet is for another ring. */ if (vring_change) returnfalse; goto mlxbf_tmfifo_desc_done;
}
/* Get the length of this desc. */
len = virtio32_to_cpu(vdev, desc->len); if (len > vring->rem_len)
len = vring->rem_len;
/* Rx/Tx one word (8 bytes) if not done. */ if (vring->cur_len < len) {
mlxbf_tmfifo_rxtx_word(vring, desc, is_rx, len);
(*avail)--;
}
/* Check again whether it's done. */ if (vring->cur_len == len) {
vring->cur_len = 0;
vring->rem_len -= len;
/* Get the next desc on the chain. */ if (!IS_VRING_DROP(vring) && vring->rem_len > 0 &&
(virtio16_to_cpu(vdev, desc->flags) & VRING_DESC_F_NEXT)) {
idx = virtio16_to_cpu(vdev, desc->next);
desc = &vr->desc[idx]; goto mlxbf_tmfifo_desc_done;
}
/* Only handle Tx timeout for network vdev. */ if (vring->vdev_id != VIRTIO_ID_NET) return;
/* Initialize the timeout or return if not expired. */ if (!vring->tx_timeout) { /* Initialize the timeout. */
vring->tx_timeout = jiffies +
msecs_to_jiffies(TMFIFO_TX_TIMEOUT); return;
} elseif (time_before(jiffies, vring->tx_timeout)) { /* Return if not timeout yet. */ return;
}
/* * Drop the packet after timeout. The outstanding packet is * released and the remaining bytes will be sent with padding byte 0x00 * as a recovery. On the peer(host) side, the padding bytes 0x00 will be * either dropped directly, or appended into existing outstanding packet * thus dropped as corrupted network packet.
*/
vring->rem_padding = round_up(vring->rem_len, sizeof(u64));
mlxbf_tmfifo_release_pkt(vring);
vring->cur_len = 0;
vring->rem_len = 0;
vring->fifo->vring[0] = NULL;
/* * Make sure the load/store are in order before * returning back to virtio.
*/
virtio_mb(false);
/* Rx & Tx processing of a queue. */ staticvoid mlxbf_tmfifo_rxtx(struct mlxbf_tmfifo_vring *vring, bool is_rx)
{ int avail = 0, devid = vring->vdev_id; struct mlxbf_tmfifo *fifo; bool more;
fifo = vring->fifo;
/* Return if vdev is not ready. */ if (!fifo || !fifo->vdev[devid]) return;
/* Return if another vring is running. */ if (fifo->vring[is_rx] && fifo->vring[is_rx] != vring) return;
/* Only handle console and network for now. */ if (WARN_ON(devid != VIRTIO_ID_NET && devid != VIRTIO_ID_CONSOLE)) return;
do {
retry: /* Get available FIFO space. */ if (avail == 0) { if (is_rx)
avail = mlxbf_tmfifo_get_rx_avail(fifo); else
avail = mlxbf_tmfifo_get_tx_avail(fifo, devid); if (avail <= 0) break;
}
/* Insert paddings for discarded Tx packet. */ if (!is_rx) {
vring->tx_timeout = 0; while (vring->rem_padding >= sizeof(u64)) {
writeq(0, vring->fifo->tx.data);
vring->rem_padding -= sizeof(u64); if (--avail == 0) goto retry;
}
}
/* Console output always comes from the Tx buffer. */ if (!is_rx && devid == VIRTIO_ID_CONSOLE) {
mlxbf_tmfifo_console_tx(fifo, avail); break;
}
/* Handle one descriptor. */
more = mlxbf_tmfifo_rxtx_one_desc(vring, is_rx, &avail);
} while (more);
/* Handle Rx or Tx queues. */ staticvoid mlxbf_tmfifo_work_rxtx(struct mlxbf_tmfifo *fifo, int queue_id, int irq_id, bool is_rx)
{ struct mlxbf_tmfifo_vdev *tm_vdev; struct mlxbf_tmfifo_vring *vring; int i;
if (!test_and_clear_bit(irq_id, &fifo->pend_events) ||
!fifo->irq_info[irq_id].irq) return;
for (i = 0; i < MLXBF_TMFIFO_VDEV_MAX; i++) {
tm_vdev = fifo->vdev[i]; if (tm_vdev) {
vring = &tm_vdev->vrings[queue_id]; if (vring->vq)
mlxbf_tmfifo_rxtx(vring, is_rx);
}
}
}
/* Work handler for Rx and Tx case. */ staticvoid mlxbf_tmfifo_work_handler(struct work_struct *work)
{ struct mlxbf_tmfifo *fifo;
fifo = container_of(work, struct mlxbf_tmfifo, work); if (!fifo->is_ready) return;
mutex_lock(&fifo->lock);
/* Tx (Send data to the TmFifo). */
mlxbf_tmfifo_work_rxtx(fifo, MLXBF_TMFIFO_VRING_TX,
MLXBF_TM_TX_LWM_IRQ, false);
/* Rx (Receive data from the TmFifo). */
mlxbf_tmfifo_work_rxtx(fifo, MLXBF_TMFIFO_VRING_RX,
MLXBF_TM_RX_HWM_IRQ, true);
mutex_unlock(&fifo->lock);
}
/* The notify function is called when new buffers are posted. */ staticbool mlxbf_tmfifo_virtio_notify(struct virtqueue *vq)
{ struct mlxbf_tmfifo_vring *vring = vq->priv; struct mlxbf_tmfifo_vdev *tm_vdev; struct mlxbf_tmfifo *fifo; unsignedlong flags;
fifo = vring->fifo;
/* * Virtio maintains vrings in pairs, even number ring for Rx * and odd number ring for Tx.
*/ if (vring->index & BIT(0)) { /* * Console could make blocking call with interrupts disabled. * In such case, the vring needs to be served right away. For * other cases, just set the TX LWM bit to start Tx in the * worker handler.
*/ if (vring->vdev_id == VIRTIO_ID_CONSOLE) {
spin_lock_irqsave(&fifo->spin_lock[0], flags);
tm_vdev = fifo->vdev[VIRTIO_ID_CONSOLE];
mlxbf_tmfifo_console_output(tm_vdev, vring);
spin_unlock_irqrestore(&fifo->spin_lock[0], flags);
set_bit(MLXBF_TM_TX_LWM_IRQ, &fifo->pend_events);
} elseif (test_and_set_bit(MLXBF_TM_TX_LWM_IRQ,
&fifo->pend_events)) { returntrue;
}
} else { if (test_and_set_bit(MLXBF_TM_RX_HWM_IRQ, &fifo->pend_events)) returntrue;
}
schedule_work(&fifo->work);
returntrue;
}
/* Get the array of feature bits for this device. */ static u64 mlxbf_tmfifo_virtio_get_features(struct virtio_device *vdev)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev);
return tm_vdev->features;
}
/* Confirm device features to use. */ staticint mlxbf_tmfifo_virtio_finalize_features(struct virtio_device *vdev)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev);
tm_vdev->features = vdev->features;
return 0;
}
/* Free virtqueues found by find_vqs(). */ staticvoid mlxbf_tmfifo_virtio_del_vqs(struct virtio_device *vdev)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); struct mlxbf_tmfifo_vring *vring; struct virtqueue *vq; int i;
for (i = 0; i < ARRAY_SIZE(tm_vdev->vrings); i++) {
vring = &tm_vdev->vrings[i];
/* Release the pending packet. */ if (vring->desc)
mlxbf_tmfifo_release_pkt(vring);
vq = vring->vq; if (vq) {
vring->vq = NULL;
vring_del_virtqueue(vq);
}
}
}
/* Create and initialize the virtual queues. */ staticint mlxbf_tmfifo_virtio_find_vqs(struct virtio_device *vdev, unsignedint nvqs, struct virtqueue *vqs[], struct virtqueue_info vqs_info[], struct irq_affinity *desc)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); struct mlxbf_tmfifo_vring *vring; struct virtqueue *vq; int i, ret, size;
if (nvqs > ARRAY_SIZE(tm_vdev->vrings)) return -EINVAL;
for (i = 0; i < nvqs; ++i) { struct virtqueue_info *vqi = &vqs_info[i];
if (!vqi->name) {
ret = -EINVAL; goto error;
}
vring = &tm_vdev->vrings[i];
/* Read the status byte. */ static u8 mlxbf_tmfifo_virtio_get_status(struct virtio_device *vdev)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev);
return tm_vdev->status;
}
/* Write the status byte. */ staticvoid mlxbf_tmfifo_virtio_set_status(struct virtio_device *vdev,
u8 status)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev);
tm_vdev->status = status;
}
/* Reset the device. Not much here for now. */ staticvoid mlxbf_tmfifo_virtio_reset(struct virtio_device *vdev)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev);
tm_vdev->status = 0;
}
/* Read the value of a configuration field. */ staticvoid mlxbf_tmfifo_virtio_get(struct virtio_device *vdev, unsignedint offset, void *buf, unsignedint len)
{ struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev);
if ((u64)offset + len > sizeof(tm_vdev->config)) return;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.