Thomas Huth 68c95ed1db pc-bios/s390-ccw: Fix boot problem with virtio-net devices
When we are trying to boot from virtio-net devices, the
s390-ccw bios currently leaves the virtio-net device enabled
after using it. That means that the receiving virt queues will
continue to happily write incoming network packets into memory.
This can corrupt data of the following boot process. For example,
if you set up a second guest on a virtual network and create a
lot of broadcast traffic there, e.g. with:

 ping -i 0.02 -s 1400  -b 192.168.1.255

and then you try to boot a guest with two boot devices, a network
device first (which should not be bootable) and e.g. a bootable SCSI
CD second, then this guest will fail to load the kernel from the CD
image:

 $ qemu-system-s390x -m 2G -nographic -device virtio-scsi-ccw \
    -netdev tap,id=net0 -device virtio-net-ccw,netdev=net0,bootindex=1 \
    -drive if=none,file=test.iso,format=raw,id=cd1 \
    -device scsi-cd,drive=cd1,bootindex=2
 LOADPARM=[        ]

 Network boot device detected
 Network boot starting...
   Using MAC address: 52:54:00:12:34:56
   Requesting information via DHCP: done
   Using IPv4 address: 192.168.1.76
   Using TFTP server: 192.168.1.1
 Trying pxelinux.cfg files...
   TFTP error: ICMP ERROR "port unreachable"
   Receiving data:  0 KBytes
 Repeating TFTP read request...
   TFTP error: ICMP ERROR "port unreachable"
 Failed to load OS from network.
 Failed to IPL from this network!
 LOADPARM=[        ]

 Using virtio-scsi.

 ! virtio-scsi:setup:inquiry: response VS RESP=ff !
 ERROR: No suitable device for IPL. Halting...

We really have to shut up the virtio-net devices after we're not
using it anymore. The easiest way to do this is to simply reset
the device, so let's do that now.

Reviewed-by: Jared Rossi <jrossi@linux.ibm.com>
Reviewed-by: Eric Farman <farman@linux.ibm.com>
Tested-by: Jared Rossi <jrossi@linux.ibm.com>
Message-ID: <20250116115826.192047-3-thuth@redhat.com>
Signed-off-by: Thomas Huth <thuth@redhat.com>
2025-01-22 08:28:49 +01:00

148 lines
3.7 KiB
C

/*
* Virtio-net driver for the s390-ccw firmware
*
* Copyright 2017 Thomas Huth, Red Hat Inc.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <ethernet.h>
#include "s390-ccw.h"
#include "virtio.h"
#include "s390-time.h"
#include "helper.h"
#ifndef DEBUG_VIRTIO_NET
#define DEBUG_VIRTIO_NET 0
#endif
#define VIRTIO_NET_F_MAC_BIT (1 << 5)
#define VQ_RX 0 /* Receive queue */
#define VQ_TX 1 /* Transmit queue */
struct VirtioNetHdr {
uint8_t flags;
uint8_t gso_type;
uint16_t hdr_len;
uint16_t gso_size;
uint16_t csum_start;
uint16_t csum_offset;
/*uint16_t num_buffers;*/ /* Only with VIRTIO_NET_F_MRG_RXBUF or VIRTIO1 */
};
typedef struct VirtioNetHdr VirtioNetHdr;
static uint16_t rx_last_idx; /* Last index in receive queue "used" ring */
int virtio_net_init(void *mac_addr)
{
VDev *vdev = virtio_get_device();
VRing *rxvq = &vdev->vrings[VQ_RX];
void *buf;
int i;
rx_last_idx = 0;
vdev->guest_features[0] = VIRTIO_NET_F_MAC_BIT;
virtio_setup_ccw(vdev);
if (!(vdev->guest_features[0] & VIRTIO_NET_F_MAC_BIT)) {
puts("virtio-net device does not support the MAC address feature");
return -1;
}
memcpy(mac_addr, vdev->config.net.mac, ETH_ALEN);
for (i = 0; i < 64; i++) {
buf = malloc(ETH_MTU_SIZE + sizeof(VirtioNetHdr));
IPL_assert(buf != NULL, "Can not allocate memory for receive buffers");
vring_send_buf(rxvq, buf, ETH_MTU_SIZE + sizeof(VirtioNetHdr),
VRING_DESC_F_WRITE);
}
vring_notify(rxvq);
return 0;
}
int send(int fd, const void *buf, int len, int flags)
{
VirtioNetHdr tx_hdr;
VDev *vdev = virtio_get_device();
VRing *txvq = &vdev->vrings[VQ_TX];
/* Set up header - we do not use anything special, so simply clear it */
memset(&tx_hdr, 0, sizeof(tx_hdr));
vring_send_buf(txvq, &tx_hdr, sizeof(tx_hdr), VRING_DESC_F_NEXT);
vring_send_buf(txvq, (void *)buf, len, VRING_HIDDEN_IS_CHAIN);
while (!vr_poll(txvq)) {
yield();
}
if (drain_irqs(txvq->schid)) {
puts("send: drain irqs failed");
return -1;
}
return len;
}
int recv(int fd, void *buf, int maxlen, int flags)
{
VDev *vdev = virtio_get_device();
VRing *rxvq = &vdev->vrings[VQ_RX];
int len, id;
uint8_t *pkt;
if (rx_last_idx == rxvq->used->idx) {
return 0;
}
len = rxvq->used->ring[rx_last_idx % rxvq->num].len - sizeof(VirtioNetHdr);
if (len > maxlen) {
puts("virtio-net: Receive buffer too small");
len = maxlen;
}
id = rxvq->used->ring[rx_last_idx % rxvq->num].id % rxvq->num;
pkt = (uint8_t *)(rxvq->desc[id].addr + sizeof(VirtioNetHdr));
#if DEBUG_VIRTIO_NET /* Dump packet */
int i;
printf("\nbuf %p: len=%i\n", (void *)rxvq->desc[id].addr, len);
for (i = 0; i < 64; i++) {
printf(" %02x", pkt[i]);
if ((i % 16) == 15) {
printf("\n");
}
}
printf("\n");
#endif
/* Copy data to destination buffer */
memcpy(buf, pkt, len);
/* Mark buffer as available to the host again */
rxvq->avail->ring[rxvq->avail->idx % rxvq->num] = id;
rxvq->avail->idx = rxvq->avail->idx + 1;
vring_notify(rxvq);
/* Move index to next entry */
rx_last_idx = rx_last_idx + 1;
return len;
}
void virtio_net_deinit(void)
{
virtio_reset(virtio_get_device());
}