/
githubmirror
/
ipxe
Обзор
Документация
Войти
/
githubmirror
/
ipxe
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/drivers/bus/virtio.c
790 строк
20 KB
Michael Brown
[virtio] Ignore capabilities that describe inaccessible PCI BARs
16 июн 2026, 15:17
16 июн 2026, 15:17
fe070bf
Код
Авторство
О чём код?
/* * Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>. * * This program 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 any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); FILE_SECBOOT ( PERMITTED ); /** @file * * Virtual I/O device * */ #include <string.h> #include <assert.h> #include <errno.h> #include <unistd.h> #include <ipxe/pci.h> #include <ipxe/virtio.h> /****************************************************************************** * * Original ("legacy") device operations * ****************************************************************************** */ /** * Reset device * * @v virtio Virtio device * @ret rc Return status code */ static int virtio_legacy_reset ( struct virtio_device *virtio ) { uint8_t stat; unsigned int i; /* Reset device */ iowrite8 ( 0, virtio->common + VIRTIO_LEG_STAT ); /* Wait for reset to complete */ for ( i = 0 ; i < VIRTIO_RESET_MAX_WAIT_MS ; i++ ) { stat = ioread8 ( virtio->common + VIRTIO_LEG_STAT ); if ( ! stat ) return 0; mdelay ( 1 ); } DBGC ( virtio, "VIRTIO %s could not reset device\n", virtio->name ); return -ETIMEDOUT; } /** * Report driver status * * @v virtio Virtio device * @ret stat Actual device status */ static unsigned int virtio_legacy_status ( struct virtio_device *virtio ) { /* Report device status */ iowrite8 ( virtio->stat, virtio->common + VIRTIO_LEG_STAT ); /* Read back device status */ return ioread8 ( virtio->common + VIRTIO_LEG_STAT ); } /** * Get supported features * * @v virtio Virtio device */ static void virtio_legacy_supported ( struct virtio_device *virtio ) { struct virtio_features *supported = &virtio->supported; unsigned int i; /* Get device supported features */ supported->word[0] = ioread32 ( virtio->common + VIRTIO_LEG_FEAT ); /* Legacy devices have only a single 32-bit feature register */ for ( i = 1 ; i < VIRTIO_FEATURE_WORDS ; i++ ) supported->word[i] = 0; } /** * Negotiate device features * * @v virtio Virtio device */ static void virtio_legacy_negotiate ( struct virtio_device *virtio ) { struct virtio_features *features = &virtio->features; unsigned int i; /* Set in-use features */ iowrite32 ( features->word[0], virtio->common + VIRTIO_LEG_USED ); /* Legacy devices have only a single 32-bit feature register */ for ( i = 1 ; i < VIRTIO_FEATURE_WORDS ; i++ ) assert ( features->word[i] == 0 ); } /** * Set queue size * * @v virtio Virtio device * @v queue Virtio queue * @v count Requested size */ static void virtio_legacy_size ( struct virtio_device *virtio, struct virtio_queue *queue, unsigned int count ) { size_t len; /* Select queue */ iowrite16 ( queue->index, virtio->common + VIRTIO_LEG_SEL ); /* Get (fixed) queue size */ count = ioread16 ( virtio->common + VIRTIO_LEG_SIZE ); /* Calculate queue length */ len = virtio_desc_size ( count ); len = virtio_align ( len + virtio_sq_size ( count ) ); len = virtio_align ( len + virtio_cq_size ( count ) ); /* Record queue size */ queue->count = count; queue->len = len; } /** * Enable queue * * @v virtio Virtio device * @v queue Virtio queue */ static void virtio_legacy_enable ( struct virtio_device *virtio, struct virtio_queue *queue ) { unsigned int count = queue->count; void *base = queue->desc; size_t len; /* Select queue */ iowrite16 ( queue->index, virtio->common + VIRTIO_LEG_SEL ); /* Lay out queue regions */ len = virtio_desc_size ( count ); queue->sq = ( base + len ); len = virtio_align ( len + virtio_sq_size ( count ) ); queue->cq = ( base + len ); len = virtio_align ( len + virtio_cq_size ( count ) ); assert ( len == queue->len ); /* Program queue base page address */ iowrite32 ( ( dma ( &queue->map, queue->desc ) / VIRTIO_PAGE ), virtio->common + VIRTIO_LEG_BASE ); } /** Original ("legacy") device operations */ static struct virtio_operations virtio_legacy_operations = { .reset = virtio_legacy_reset, .status = virtio_legacy_status, .supported = virtio_legacy_supported, .negotiate = virtio_legacy_negotiate, .size = virtio_legacy_size, .enable = virtio_legacy_enable, }; /****************************************************************************** * * PCI ("modern") device operations * ****************************************************************************** */ /** * Reset device * * @v virtio Virtio device * @ret rc Return status code */ static int virtio_pci_reset ( struct virtio_device *virtio ) { uint8_t stat; unsigned int i; /* Reset device */ iowrite8 ( 0, virtio->common + VIRTIO_PCI_STAT ); /* Wait for reset to complete */ for ( i = 0 ; i < VIRTIO_RESET_MAX_WAIT_MS ; i++ ) { stat = ioread8 ( virtio->common + VIRTIO_PCI_STAT ); if ( ! stat ) return 0; mdelay ( 1 ); } DBGC ( virtio, "VIRTIO %s could not reset device\n", virtio->name ); return -ETIMEDOUT; } /** * Report driver status * * @v virtio Virtio device * @ret stat Actual device status */ static unsigned int virtio_pci_status ( struct virtio_device *virtio ) { /* Report device status */ iowrite8 ( virtio->stat, virtio->common + VIRTIO_PCI_STAT ); /* Read back device status */ return ioread8 ( virtio->common + VIRTIO_PCI_STAT ); } /** * Get supported features * * @v virtio Virtio device */ static void virtio_pci_supported ( struct virtio_device *virtio ) { struct virtio_features *supported = &virtio->supported; unsigned int i; /* Get device supported features */ for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ ) { iowrite32 ( i, virtio->common + VIRTIO_PCI_FEAT_SEL ); supported->word[i] = ioread32 ( virtio->common + VIRTIO_PCI_FEAT ); } } /** * Negotiate device features * * @v virtio Virtio device */ static void virtio_pci_negotiate ( struct virtio_device *virtio ) { struct virtio_features *features = &virtio->features; unsigned int i; /* Set in-use features */ for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ ) { iowrite32 ( i, virtio->common + VIRTIO_PCI_USED_SEL ); iowrite32 ( features->word[i], virtio->common + VIRTIO_PCI_USED ); } } /** * Set queue size * * @v virtio Virtio device * @v queue Virtio queue * @v count Requested size */ static void virtio_pci_size ( struct virtio_device *virtio, struct virtio_queue *queue, unsigned int count ) { unsigned int max; size_t len; /* Select queue */ iowrite16 ( queue->index, virtio->common + VIRTIO_PCI_SEL ); /* Set queue size */ max = ioread16 ( virtio->common + VIRTIO_PCI_SIZE ); if ( count > max ) count = max; iowrite16 ( count, virtio->common + VIRTIO_PCI_SIZE ); /* Calculate queue length */ len = virtio_align ( virtio_desc_size ( count ) ); len = virtio_align ( len + virtio_sq_size ( count ) ); len = virtio_align ( len + virtio_cq_size ( count ) ); /* Record queue size */ queue->count = count; queue->len = len; } /** * Program queue address * * @v virtio Virtio device * @v queue Virtio queue * @v addr Address * @v offset Register offset */ static void virtio_pci_address ( struct virtio_device *virtio, struct virtio_queue *queue, void *addr, unsigned int offset ) { physaddr_t phys; /* Program address */ phys = dma ( &queue->map, addr ); iowrite32 ( ( phys & 0xffffffffUL ), ( virtio->common + offset + 0 ) ); if ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) ) { iowrite32 ( ( ( ( uint64_t ) phys ) >> 32 ), ( virtio->common + offset + 4 ) ); } else { iowrite32 ( 0, ( virtio->common + offset + 4 ) ); } } /** * Enable queue * * @v virtio Virtio device * @v queue Virtio queue */ static void virtio_pci_enable ( struct virtio_device *virtio, struct virtio_queue *queue ) { unsigned int count = queue->count; void *base = queue->desc; size_t len; /* Select queue */ iowrite16 ( queue->index, virtio->common + VIRTIO_PCI_SEL ); /* Lay out queue regions */ len = virtio_align ( virtio_desc_size ( count ) ); queue->sq = ( base + len ); len = virtio_align ( len + virtio_sq_size ( count ) ); queue->cq = ( base + len ); len = virtio_align ( len + virtio_cq_size ( count ) ); assert ( len == queue->len ); /* Program queue addresses */ virtio_pci_address ( virtio, queue, queue->desc, VIRTIO_PCI_DESC ); virtio_pci_address ( virtio, queue, queue->sq, VIRTIO_PCI_SQ ); virtio_pci_address ( virtio, queue, queue->cq, VIRTIO_PCI_CQ ); /* Enable queue */ iowrite16 ( 1, virtio->common + VIRTIO_PCI_ENABLE ); } /** PCI ("modern") device operations */ static struct virtio_operations virtio_pci_operations = { .reset = virtio_pci_reset, .status = virtio_pci_status, .supported = virtio_pci_supported, .negotiate = virtio_pci_negotiate, .size = virtio_pci_size, .enable = virtio_pci_enable, }; /** * Find PCI capability * * @v virtio Virtio device * @v pci PCI device * @v type Capability type * @v cap Virtio PCI capability to fill in * @ret rc Return status code */ static int virtio_pci_cap ( struct virtio_device *virtio, struct pci_device *pci, unsigned int type, struct virtio_pci_capability *cap ) { unsigned int reg; int pos; /* Scan through vendor capabilities */ for ( pos = pci_find_capability ( pci, PCI_CAP_ID_VNDR ) ; pos > 0 ; pos = pci_find_next_capability ( pci, pos, PCI_CAP_ID_VNDR ) ) { /* Check length */ pci_read_config_byte ( pci, ( pos + PCI_CAP_LEN ), &cap->len ); if ( cap->len < VIRTIO_PCI_CAP_END ) { DBGC ( virtio, "VIRTIO %s capability +%#02x too short " "(%d bytes)\n", virtio->name, pos, cap->len ); continue; } /* Read values */ pci_read_config_byte ( pci, ( pos + VIRTIO_PCI_CAP_TYPE ), &cap->type ); pci_read_config_byte ( pci, ( pos + VIRTIO_PCI_CAP_BAR ), &cap->bar ); pci_read_config_dword ( pci, ( pos + VIRTIO_PCI_CAP_OFFSET ), &cap->offset ); /* Check type */ if ( cap->type != type ) continue; DBGC2 ( virtio, "VIRTIO %s capability type %d BAR%d+%#04x\n", virtio->name, type, cap->bar, cap->offset ); /* Check BAR */ reg = PCI_BASE_ADDRESS ( cap->bar ); if ( reg > PCI_BASE_ADDRESS_5 ) continue; /* Check BAR accessibility */ if ( ! pci_bar_start ( pci, reg ) ) { DBGC ( virtio, "VIRTIO %s capability type %d BAR%d is " "not usable\n", virtio->name, type, cap->bar ); continue; } /* Success */ cap->pos = pos; return 0; } DBGC ( virtio, "VIRTIO %s has no usable capability type %d\n", virtio->name, type ); cap->pos = 0; return -ENOENT; } /** * Map PCI capability * * @v virtio Virtio device * @v pci PCI device * @v cap Virtio PCI capability * @ret io_addr I/O address, or NULL on error */ static void * virtio_pci_map_cap ( struct virtio_device *virtio, struct pci_device *pci, struct virtio_pci_capability *cap ) { unsigned long addr; unsigned int reg; int is_io_bar; void *io_addr; /* Get BAR start address and type */ reg = PCI_BASE_ADDRESS ( cap->bar ); addr = pci_bar_start ( pci, reg ); if ( ! addr ) { DBGC ( virtio, "VIRTIO %s BAR%d is not usable\n", virtio->name, cap->bar ); return NULL; } /* Map memory or I/O BAR */ addr += cap->offset; is_io_bar = pci_bar_is_io ( pci, reg ); io_addr = ( is_io_bar ? ( ( void * ) addr ) : pci_ioremap ( pci, addr, VIRTIO_PAGE ) ); if ( ! io_addr ) { DBGC ( virtio, "VIRTIO %s could not map BAR%d+%#04x\n", virtio->name, cap->bar, cap->offset ); return NULL; } DBGC2 ( virtio, "VIRTIO %s mapped BAR%d+%#04x (%s %#08lx)\n", virtio->name, cap->bar, cap->offset, ( is_io_bar ? "IO" : "MEM" ), addr ); return io_addr; } /** * Map PCI device * * @v virtio Virtio device * @v pci PCI device * @ret rc Return status code */ int virtio_pci_map ( struct virtio_device *virtio, struct pci_device *pci ) { struct virtio_pci_capability common; struct virtio_pci_capability notify; struct virtio_pci_capability device; unsigned int msix; uint32_t mult; uint16_t ctrl; int rc; /* Initialise device */ virtio->name = pci->dev.name; virtio->dma = &pci->dma; /* Fix up PCI device */ adjust_pci_device ( pci ); /* Check if MSI-X is enabled */ msix = pci_find_capability ( pci, PCI_CAP_ID_MSIX ); if ( msix ) { pci_read_config_word ( pci, msix, &ctrl ); if ( ! ( ctrl & PCI_MSIX_CTRL_ENABLE ) ) msix = 0; } /* Locate virtio capabilities */ virtio_pci_cap ( virtio, pci, VIRTIO_PCI_CAP_TYPE_COMMON, &common ); virtio_pci_cap ( virtio, pci, VIRTIO_PCI_CAP_TYPE_NOTIFY, ¬ify ); virtio_pci_cap ( virtio, pci, VIRTIO_PCI_CAP_TYPE_DEVICE, &device ); /* Use modern interface if available */ if ( common.pos && notify.pos && device.pos && ( notify.len >= VIRTIO_PCI_CAP_NOTIFY_END ) ) { /* Use modern interface */ virtio->op = &virtio_pci_operations; dma_set_mask_64bit ( virtio->dma ); /* Read notification doorbell multiplier */ pci_read_config_dword ( pci, ( notify.pos + VIRTIO_PCI_CAP_NOTIFY_MULT ), &mult ); virtio->multiplier = mult; DBGC ( virtio, "VIRTIO %s using modern interface (mult x%d)\n", virtio->name, virtio->multiplier ); } else { /* Use legacy interface */ virtio->op = &virtio_legacy_operations; common.bar = 0; common.offset = 0; notify.bar = 0; notify.offset = VIRTIO_LEG_DB; device.bar = 0; device.offset = ( msix ? VIRTIO_LEG_DEV_MSIX : VIRTIO_LEG_DEV ); DBGC ( virtio, "VIRTIO %s using legacy interface (MSI-X " "%sabled)\n", virtio->name, ( msix ? "en" : "dis" ) ); } /* Map registers */ virtio->common = virtio_pci_map_cap ( virtio, pci, &common ); if ( ! virtio->common ) { rc = -ENODEV; goto err_common; } virtio->notify = virtio_pci_map_cap ( virtio, pci, ¬ify ); if ( ! virtio->notify ) { rc = -ENODEV; goto err_notify; } virtio->device = virtio_pci_map_cap ( virtio, pci, &device ); if ( ! virtio->device ) { rc = -ENODEV; goto err_device; } return 0; iounmap ( virtio->device ); err_device: iounmap ( virtio->notify ); err_notify: iounmap ( virtio->common ); err_common: return rc; } /****************************************************************************** * * Transport-independent operations * ****************************************************************************** */ /** * Reset device * * @v virtio Virtio device * @ret rc Return status code */ int virtio_reset ( struct virtio_device *virtio ) { int rc; /* Clear driver status */ virtio->stat = 0; /* Reset device */ if ( ( rc = virtio->op->reset ( virtio ) ) != 0 ) { DBGC ( virtio, "VIRTIO %s could not reset: %s\n", virtio->name, strerror ( rc ) ); return rc; } return 0; } /** * Report driver status * * @v virtio Virtio device * @v stat Additional driver status bits * @ret stat Actual device status */ unsigned int virtio_status ( struct virtio_device *virtio, unsigned int stat ) { /* Set new driver status bits */ virtio->stat |= stat; /* Report driver status */ return virtio->op->status ( virtio ); } /** * Negotiate features * * @v virtio Virtio device * @v driver Driver supported features */ static void virtio_negotiate ( struct virtio_device *virtio, const struct virtio_features *driver ) { struct virtio_features *device = &virtio->supported; struct virtio_features *features = &virtio->features; unsigned int i; /* Get device supported features */ virtio->op->supported ( virtio ); /* Negotiate mutually supported features */ for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ ) features->word[i] = ( device->word[i] & driver->word[i] ); virtio->op->negotiate ( virtio ); /* Show features */ DBGC ( virtio, "VIRTIO %s features", virtio->name ); for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ ) DBGC ( virtio, "%s%08x", ( i ? ":" : " " ), device->word[i] ); DBGC ( virtio, " /" ); for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ ) DBGC ( virtio, "%s%08x", ( i ? ":" : " " ), features->word[i] ); DBGC ( virtio, "\n" ); } /** * Initialise device * * @v virtio Virtio device * @v driver Driver supported features * @ret rc Return status code */ int virtio_init ( struct virtio_device *virtio, const struct virtio_features *driver ) { unsigned int stat; int rc; /* Reset device */ if ( ( rc = virtio_reset ( virtio ) ) != 0 ) goto err_reset; /* Acknowledge device existence */ virtio_status ( virtio, VIRTIO_STAT_ACKNOWLEDGE ); /* Report driver existence */ virtio_status ( virtio, VIRTIO_STAT_DRIVER ); /* Negotiate features */ virtio_negotiate ( virtio, driver ); /* Report feature negotiation completion, if applicable */ if ( virtio->features.word[1] & VIRTIO_FEAT1_MODERN ) { stat = virtio_status ( virtio, VIRTIO_STAT_FEATURES_OK ); if ( ! ( stat & VIRTIO_STAT_FEATURES_OK ) ) { DBGC ( virtio, "VIRTIO %s did not accept features\n", virtio->name ); rc = -ENOTSUP; goto err_features; } } return 0; err_features: virtio_reset ( virtio ); err_reset: virtio_status ( virtio, VIRTIO_STAT_FAIL ); return rc; } /** * Enable queue * * @v virtio Virtio device * @v queue Virtio queue * @v count Requested queue size * @ret rc Return status code */ int virtio_enable ( struct virtio_device *virtio, struct virtio_queue *queue, unsigned int count ) { unsigned int offset; int rc; /* Reset counters */ queue->prod = 0; queue->cons = 0; /* Determine queue size */ virtio->op->size ( virtio, queue, count ); if ( ( queue->count == 0 ) || ( queue->count & ( queue->count - 1 ) ) ) { DBGC ( virtio, "VIRTIO %s Q%d invalid size %d\n", virtio->name, queue->index, queue->count ); rc = -ENODEV; goto err_count; } queue->mask = ( queue->count - 1 ); /* Allocate and initialise queue */ queue->desc = dma_alloc ( virtio->dma, &queue->map, queue->len, VIRTIO_PAGE ); if ( ! queue->desc ) { rc = -ENOMEM; goto err_alloc; } memset ( queue->desc, 0, queue->len ); /* Enable queue */ virtio->op->enable ( virtio, queue ); DBGC ( virtio, "VIRTIO %s Q%d %dx descriptors at [%#08lx,%#08lx)\n", virtio->name, queue->index, queue->count, virt_to_phys ( queue->desc ), ( virt_to_phys ( queue->desc ) + virtio_desc_size ( queue->count ) ) ); DBGC ( virtio, "VIRTIO %s Q%d %dx submissions at [%#08lx,%#08lx)\n", virtio->name, queue->index, queue->count, virt_to_phys ( queue->sq ), ( virt_to_phys ( queue->sq ) + virtio_sq_size ( queue->count ) ) ); DBGC ( virtio, "VIRTIO %s Q%d %dx completions at [%#08lx,%#08lx)\n", virtio->name, queue->index, queue->count, virt_to_phys ( queue->cq ), ( virt_to_phys ( queue->cq ) + virtio_cq_size ( queue->count ) ) ); /* Calculate doorbell register address */ offset = ( queue->index * virtio->multiplier ); queue->db = ( virtio->notify + offset ); DBGC ( virtio, "VIRTIO %s Q%d doorbell at +%#04x\n", virtio->name, queue->index, offset ); return 0; dma_free ( &queue->map, queue->desc, queue->len ); queue->desc = NULL; err_alloc: err_count: return rc; } /** * Free queue * * @v virtio Virtio device * @v queue Virtio queue */ void virtio_free ( struct virtio_device *virtio, struct virtio_queue *queue ) { /* Free queue */ if ( queue->desc ) { dma_free ( &queue->map, queue->desc, queue->len ); queue->desc = NULL; DBGC ( virtio, "VIRTIO %s Q%d freed\n", virtio->name, queue->index ); } } /** * Unmap device * * @v virtio Virtio device */ void virtio_unmap ( struct virtio_device *virtio ) { /* Unmap device-specific registers */ iounmap ( virtio->device ); /* Unmap notification doorbells */ iounmap ( virtio->notify ); /* Unmap common registers */ iounmap ( virtio->common ); }