diff options
| author | Simon Rettberg | 2019-08-02 10:04:22 +0200 |
|---|---|---|
| committer | Simon Rettberg | 2019-08-02 10:04:22 +0200 |
| commit | bebc4ff01ee9ca2b042cf165b56bf9d3b5cb5333 (patch) | |
| tree | 6a7538ee3a4e5daf2d98d76b47e95b859c5aa265 /src | |
| parent | [efi] Add very ugly hack to use HW RNG on EFI (diff) | |
| parent | [build] Do not apply WORKAROUND_CFLAGS for host compiler (diff) | |
| download | ipxe-bebc4ff01ee9ca2b042cf165b56bf9d3b5cb5333.tar.gz ipxe-bebc4ff01ee9ca2b042cf165b56bf9d3b5cb5333.tar.xz ipxe-bebc4ff01ee9ca2b042cf165b56bf9d3b5cb5333.zip | |
Merge branch 'master' into openslx
Diffstat (limited to 'src')
47 files changed, 3763 insertions, 387 deletions
diff --git a/src/Makefile.housekeeping b/src/Makefile.housekeeping index f8334921b..1b175b950 100644 --- a/src/Makefile.housekeeping +++ b/src/Makefile.housekeeping @@ -185,6 +185,15 @@ WNST_TEST = $(CC) -Wstringop-truncation -x c -c /dev/null -o /dev/null \ >/dev/null 2>&1 WNST_FLAGS := $(shell $(WNST_TEST) && $(ECHO) '-Wno-stringop-truncation') WORKAROUND_CFLAGS += $(WNST_FLAGS) + +# gcc 9.1 generates warnings for taking address of packed member which +# may result in an unaligned pointer value. Inhibit the warnings. +# +WNAPM_TEST = $(CC) -Wno-address-of-packed-member -x c -c /dev/null \ + -o /dev/null >/dev/null 2>&1 +WNAPM_FLAGS := $(shell $(WNAPM_TEST) && \ + $(ECHO) '-Wno-address-of-packed-member') +WORKAROUND_CFLAGS += $(WNAPM_FLAGS) endif # Some versions of gas choke on division operators, treating them as @@ -445,7 +454,7 @@ endif CFLAGS += $(WORKAROUND_CFLAGS) $(EXTRA_CFLAGS) ASFLAGS += $(WORKAROUND_ASFLAGS) $(EXTRA_ASFLAGS) LDFLAGS += $(WORKAROUND_LDFLAGS) $(EXTRA_LDFLAGS) -HOST_CFLAGS += $(WORKAROUND_CFLAGS) -O2 -g +HOST_CFLAGS += -O2 -g # Inhibit -Werror if NO_WERROR is specified on make command line # diff --git a/src/arch/arm/include/ipxe/arm_io.h b/src/arch/arm/include/ipxe/arm_io.h index f8765af75..105f22bfb 100644 --- a/src/arch/arm/include/ipxe/arm_io.h +++ b/src/arch/arm/include/ipxe/arm_io.h @@ -43,43 +43,84 @@ IOAPI_INLINE ( arm, bus_to_phys ) ( unsigned long bus_addr ) { * */ -#define ARM_READX( _api_func, _type, _insn_suffix, _reg_prefix ) \ +#define ARM_READX( _suffix, _type, _insn_suffix, _reg_prefix ) \ static inline __always_inline _type \ -IOAPI_INLINE ( arm, _api_func ) ( volatile _type *io_addr ) { \ +IOAPI_INLINE ( arm, read ## _suffix ) ( volatile _type *io_addr ) { \ _type data; \ __asm__ __volatile__ ( "ldr" _insn_suffix " %" _reg_prefix "0, %1" \ : "=r" ( data ) : "Qo" ( *io_addr ) ); \ return data; \ } #ifdef __aarch64__ -ARM_READX ( readb, uint8_t, "b", "w" ); -ARM_READX ( readw, uint16_t, "h", "w" ); -ARM_READX ( readl, uint32_t, "", "w" ); -ARM_READX ( readq, uint64_t, "", "" ); +ARM_READX ( b, uint8_t, "b", "w" ); +ARM_READX ( w, uint16_t, "h", "w" ); +ARM_READX ( l, uint32_t, "", "w" ); +ARM_READX ( q, uint64_t, "", "" ); #else -ARM_READX ( readb, uint8_t, "b", "" ); -ARM_READX ( readw, uint16_t, "h", "" ); -ARM_READX ( readl, uint32_t, "", "" ); +ARM_READX ( b, uint8_t, "b", "" ); +ARM_READX ( w, uint16_t, "h", "" ); +ARM_READX ( l, uint32_t, "", "" ); #endif -#define ARM_WRITEX( _api_func, _type, _insn_suffix, _reg_prefix ) \ +#define ARM_WRITEX( _suffix, _type, _insn_suffix, _reg_prefix ) \ static inline __always_inline void \ -IOAPI_INLINE ( arm, _api_func ) ( _type data, volatile _type *io_addr ) { \ +IOAPI_INLINE ( arm, write ## _suffix ) ( _type data, \ + volatile _type *io_addr ) { \ __asm__ __volatile__ ( "str" _insn_suffix " %" _reg_prefix "0, %1" \ : : "r" ( data ), "Qo" ( *io_addr ) ); \ } #ifdef __aarch64__ -ARM_WRITEX ( writeb, uint8_t, "b", "w" ); -ARM_WRITEX ( writew, uint16_t, "h", "w" ); -ARM_WRITEX ( writel, uint32_t, "", "w" ); -ARM_WRITEX ( writeq, uint64_t, "", "" ); +ARM_WRITEX ( b, uint8_t, "b", "w" ); +ARM_WRITEX ( w, uint16_t, "h", "w" ); +ARM_WRITEX ( l, uint32_t, "", "w" ); +ARM_WRITEX ( q, uint64_t, "", "" ); #else -ARM_WRITEX ( writeb, uint8_t, "b", "" ); -ARM_WRITEX ( writew, uint16_t, "h", "" ); -ARM_WRITEX ( writel, uint32_t, "", "" ); +ARM_WRITEX ( b, uint8_t, "b", "" ); +ARM_WRITEX ( w, uint16_t, "h", "" ); +ARM_WRITEX ( l, uint32_t, "", "" ); #endif /* + * Dummy PIO reads and writes up to 32 bits + * + * There is no common standard for I/O-space access for ARM, and + * non-MMIO peripherals are vanishingly rare. Provide dummy + * implementations that will allow code to link and should cause + * drivers to simply fail to detect hardware at runtime. + * + */ + +#define ARM_INX( _suffix, _type ) \ +static inline __always_inline _type \ +IOAPI_INLINE ( arm, in ## _suffix ) ( volatile _type *io_addr __unused) { \ + return ~( (_type) 0 ); \ +} \ +static inline __always_inline void \ +IOAPI_INLINE ( arm, ins ## _suffix ) ( volatile _type *io_addr __unused, \ + _type *data, unsigned int count ) { \ + memset ( data, 0xff, count * sizeof ( *data ) ); \ +} +ARM_INX ( b, uint8_t ); +ARM_INX ( w, uint16_t ); +ARM_INX ( l, uint32_t ); + +#define ARM_OUTX( _suffix, _type ) \ +static inline __always_inline void \ +IOAPI_INLINE ( arm, out ## _suffix ) ( _type data __unused, \ + volatile _type *io_addr __unused ) { \ + /* Do nothing */ \ +} \ +static inline __always_inline void \ +IOAPI_INLINE ( arm, outs ## _suffix ) ( volatile _type *io_addr __unused, \ + const _type *data __unused, \ + unsigned int count __unused ) { \ + /* Do nothing */ \ +} +ARM_OUTX ( b, uint8_t ); +ARM_OUTX ( w, uint16_t ); +ARM_OUTX ( l, uint32_t ); + +/* * Slow down I/O * */ diff --git a/src/config/config_fdt.c b/src/config/config_fdt.c new file mode 100644 index 000000000..e8d425933 --- /dev/null +++ b/src/config/config_fdt.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 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 (at your option) 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 ); + +#include <config/fdt.h> + +/** @file + * + * Flattened Device Tree configuration options + * + */ + +PROVIDE_REQUIRING_SYMBOL(); + +/* + * Drag in devicetree sources + */ +#ifdef FDT_EFI +REQUIRE_OBJECT ( efi_fdt ); +#endif diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index 74effa425..53a7a7b4b 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -22,6 +22,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define TIME_EFI #define REBOOT_EFI #define ACPI_EFI +#define FDT_EFI #define DOWNLOAD_PROTO_FILE /* Local filesystem access */ diff --git a/src/config/fdt.h b/src/config/fdt.h new file mode 100644 index 000000000..4d13e0535 --- /dev/null +++ b/src/config/fdt.h @@ -0,0 +1,16 @@ +#ifndef CONFIG_FDT_H +#define CONFIG_FDT_H + +/** @file + * + * Flattened Device Tree configuration + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <config/defaults.h> + +#include <config/local/fdt.h> + +#endif /* CONFIG_FDT_H */ diff --git a/src/config/rpi/colour.h b/src/config/rpi/colour.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/config/rpi/colour.h diff --git a/src/config/rpi/console.h b/src/config/rpi/console.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/config/rpi/console.h diff --git a/src/config/rpi/crypto.h b/src/config/rpi/crypto.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/config/rpi/crypto.h diff --git a/src/config/rpi/general.h b/src/config/rpi/general.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/config/rpi/general.h diff --git a/src/config/rpi/serial.h b/src/config/rpi/serial.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/config/rpi/serial.h diff --git a/src/config/rpi/settings.h b/src/config/rpi/settings.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/config/rpi/settings.h diff --git a/src/config/rpi/sideband.h b/src/config/rpi/sideband.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/config/rpi/sideband.h diff --git a/src/config/rpi/usb.h b/src/config/rpi/usb.h new file mode 100644 index 000000000..f17ea0de3 --- /dev/null +++ b/src/config/rpi/usb.h @@ -0,0 +1,13 @@ +/* + * Use EFI_USB_IO_PROTOCOL + * + * The Raspberry Pi uses an embedded DesignWare USB controller for + * which we do not have a native driver. Use via the + * EFI_USB_IO_PROTOCOL driver instead. + * + */ +#undef USB_HCD_XHCI +#undef USB_HCD_EHCI +#undef USB_HCD_UHCI +#define USB_HCD_USBIO +#undef USB_EFI diff --git a/src/core/fdt.c b/src/core/fdt.c new file mode 100644 index 000000000..f439422cf --- /dev/null +++ b/src/core/fdt.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2019 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 (at your option) 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 ); + +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/netdevice.h> +#include <ipxe/fdt.h> + +/** @file + * + * Flattened Device Tree + * + */ + +/** The system flattened device tree (if present) */ +static struct fdt fdt; + +/** A position within a device tree */ +struct fdt_cursor { + /** Offset within structure block */ + unsigned int offset; + /** Tree depth */ + int depth; +}; + +/** A lexical descriptor */ +struct fdt_descriptor { + /** Node or property name (if applicable) */ + const char *name; + /** Property data (if applicable) */ + const void *data; + /** Length of property data (if applicable) */ + size_t len; +}; + +/** + * Check if device tree exists + * + * @v has_fdt Device tree exists + */ +static inline __attribute__ (( always_inline )) int fdt_exists ( void ) { + + return ( fdt.hdr != NULL ); +} + +/** + * Traverse device tree + * + * @v pos Position within device tree + * @v desc Lexical descriptor to fill in + * @ret rc Return status code + */ +static int fdt_traverse ( struct fdt_cursor *pos, + struct fdt_descriptor *desc ) { + const fdt_token_t *token; + const void *data; + const struct fdt_prop *prop; + unsigned int name_off; + size_t remaining; + size_t len; + + /* Sanity checks */ + assert ( pos->offset < fdt.len ); + assert ( ( pos->offset & ( FDT_STRUCTURE_ALIGN - 1 ) ) == 0 ); + + /* Clear descriptor */ + memset ( desc, 0, sizeof ( *desc ) ); + + /* Locate token and calculate remaining space */ + token = ( fdt.raw + fdt.structure + pos->offset ); + remaining = ( fdt.len - pos->offset ); + if ( remaining < sizeof ( *token ) ) { + DBGC ( &fdt, "FDT truncated tree at +%#04x\n", pos->offset ); + return -EINVAL; + } + remaining -= sizeof ( *token ); + data = ( ( ( const void * ) token ) + sizeof ( *token ) ); + len = 0; + + /* Handle token */ + switch ( *token ) { + + case cpu_to_be32 ( FDT_BEGIN_NODE ): + + /* Start of node */ + desc->name = data; + len = ( strnlen ( desc->name, remaining ) + 1 /* NUL */ ); + if ( remaining < len ) { + DBGC ( &fdt, "FDT unterminated node name at +%#04x\n", + pos->offset ); + return -EINVAL; + } + pos->depth++; + break; + + case cpu_to_be32 ( FDT_END_NODE ): + + /* End of node */ + if ( pos->depth < 0 ) { + DBGC ( &fdt, "FDT spurious node end at +%#04x\n", + pos->offset ); + return -EINVAL; + } + pos->depth--; + if ( pos->depth < 0 ) { + /* End of (sub)tree */ + return -ENOENT; + } + break; + + case cpu_to_be32 ( FDT_PROP ): + + /* Property */ + prop = data; + if ( remaining < sizeof ( *prop ) ) { + DBGC ( &fdt, "FDT truncated property at +%#04x\n", + pos->offset ); + return -EINVAL; + } + desc->data = ( ( ( const void * ) prop ) + sizeof ( *prop ) ); + desc->len = be32_to_cpu ( prop->len ); + len = ( sizeof ( *prop ) + desc->len ); + if ( remaining < len ) { + DBGC ( &fdt, "FDT overlength property at +%#04x\n", + pos->offset ); + return -EINVAL; + } + name_off = be32_to_cpu ( prop->name_off ); + if ( name_off > fdt.strings_len ) { + DBGC ( &fdt, "FDT property name outside strings " + "block at +%#04x\n", pos->offset ); + return -EINVAL; + } + desc->name = ( fdt.raw + fdt.strings + name_off ); + break; + + case cpu_to_be32 ( FDT_NOP ): + + /* Do nothing */ + break; + + default: + + /* Unrecognised or unexpected token */ + DBGC ( &fdt, "FDT unexpected token %#08x at +%#04x\n", + be32_to_cpu ( *token ), pos->offset ); + return -EINVAL; + } + + /* Update cursor */ + len = ( ( len + FDT_STRUCTURE_ALIGN - 1 ) & + ~( FDT_STRUCTURE_ALIGN - 1 ) ); + pos->offset += ( sizeof ( *token ) + len ); + + /* Sanity checks */ + assert ( pos->offset <= fdt.len ); + + return 0; +} + +/** + * Find child node + * + * @v offset Starting node offset + * @v name Node name + * @v child Child node offset to fill in + * @ret rc Return status code + */ +static int fdt_child ( unsigned int offset, const char *name, + unsigned int *child ) { + struct fdt_cursor pos; + struct fdt_descriptor desc; + unsigned int orig_offset; + int rc; + + /* Record original offset (for debugging) */ + orig_offset = offset; + + /* Initialise cursor */ + pos.offset = offset; + pos.depth = -1; + + /* Find child node */ + while ( 1 ) { + + /* Record current offset */ + *child = pos.offset; + + /* Traverse tree */ + if ( ( rc = fdt_traverse ( &pos, &desc ) ) != 0 ) { + DBGC ( &fdt, "FDT +%#04x has no child node \"%s\": " + "%s\n", orig_offset, name, strerror ( rc ) ); + return rc; + } + + /* Check for matching immediate child node */ + if ( ( pos.depth == 1 ) && desc.name && ( ! desc.data ) ) { + DBGC2 ( &fdt, "FDT +%#04x has child node \"%s\"\n", + orig_offset, desc.name ); + if ( strcmp ( name, desc.name ) == 0 ) { + DBGC2 ( &fdt, "FDT +%#04x found child node " + "\"%s\" at +%#04x\n", orig_offset, + desc.name, *child ); + return 0; + } + } + } +} + +/** + * Find node by path + * + * @v path Node path + * @v offset Offset to fill in + * @ret rc Return status code + */ +int fdt_path ( const char *path, unsigned int *offset ) { + char *tmp = ( ( char * ) path ); + char *del; + int rc; + + /* Initialise offset */ + *offset = 0; + + /* Traverse tree one path segment at a time */ + while ( *tmp ) { + + /* Skip any leading '/' */ + while ( *tmp == '/' ) + tmp++; + + /* Find next '/' delimiter and convert to NUL */ + del = strchr ( tmp, '/' ); + if ( del ) + *del = '\0'; + + /* Find child and restore delimiter */ + rc = fdt_child ( *offset, tmp, offset ); + if ( del ) + *del = '/'; + if ( rc != 0 ) + return rc; + + /* Move to next path component, if any */ + while ( *tmp && ( *tmp != '/' ) ) + tmp++; + } + + DBGC2 ( &fdt, "FDT found path \"%s\" at +%#04x\n", path, *offset ); + return 0; +} + +/** + * Find node by alias + * + * @v name Alias name + * @v offset Offset to fill in + * @ret rc Return status code + */ +int fdt_alias ( const char *name, unsigned int *offset ) { + const char *alias; + int rc; + + /* Locate "/aliases" node */ + if ( ( rc = fdt_child ( 0, "aliases", offset ) ) != 0 ) + return rc; + + /* Locate alias property */ + if ( ( alias = fdt_string ( *offset, name ) ) == NULL ) + return -ENOENT; + DBGC ( &fdt, "FDT alias \"%s\" is \"%s\"\n", name, alias ); + + /* Locate aliased node */ + if ( ( rc = fdt_path ( alias, offset ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Find property + * + * @v offset Starting node offset + * @v name Property name + * @v desc Lexical descriptor to fill in + * @ret rc Return status code + */ +static int fdt_property ( unsigned int offset, const char *name, + struct fdt_descriptor *desc ) { + struct fdt_cursor pos; + int rc; + + /* Initialise cursor */ + pos.offset = offset; + pos.depth = -1; + + /* Find property */ + while ( 1 ) { + + /* Traverse tree */ + if ( ( rc = fdt_traverse ( &pos, desc ) ) != 0 ) { + DBGC ( &fdt, "FDT +%#04x has no property \"%s\": %s\n", + offset, name, strerror ( rc ) ); + return rc; + } + + /* Check for matching immediate child property */ + if ( ( pos.depth == 0 ) && desc->data ) { + DBGC2 ( &fdt, "FDT +%#04x has property \"%s\" len " + "%#zx\n", offset, desc->name, desc->len ); + if ( strcmp ( name, desc->name ) == 0 ) { + DBGC2 ( &fdt, "FDT +%#04x found property " + "\"%s\"\n", offset, desc->name ); + DBGC2_HDA ( &fdt, 0, desc->data, desc->len ); + return 0; + } + } + } +} + +/** + * Find string property + * + * @v offset Starting node offset + * @v name Property name + * @ret string String property, or NULL on error + */ +const char * fdt_string ( unsigned int offset, const char *name ) { + struct fdt_descriptor desc; + int rc; + + /* Find property */ + if ( ( rc = fdt_property ( offset, name, &desc ) ) != 0 ) + return NULL; + + /* Check NUL termination */ + if ( strnlen ( desc.data, desc.len ) == desc.len ) { + DBGC ( &fdt, "FDT unterminated string property \"%s\"\n", + name ); + return NULL; + } + + return desc.data; +} + +/** + * Get MAC address from property + * + * @v offset Starting node offset + * @v netdev Network device + * @ret rc Return status code + */ +int fdt_mac ( unsigned int offset, struct net_device *netdev ) { + struct fdt_descriptor desc; + size_t len; + int rc; + + /* Find applicable MAC address property */ + if ( ( ( rc = fdt_property ( offset, "mac-address", &desc ) ) != 0 ) && + ( ( rc = fdt_property ( offset, "local-mac-address", + &desc ) ) != 0 ) ) { + return rc; + } + + /* Check length */ + len = netdev->ll_protocol->hw_addr_len; + if ( len != desc.len ) { + DBGC ( &fdt, "FDT malformed MAC address \"%s\":\n", + desc.name ); + DBGC_HDA ( &fdt, 0, desc.data, desc.len ); + return -ERANGE; + } + + /* Fill in MAC address */ + memcpy ( netdev->hw_addr, desc.data, len ); + + return 0; +} + +/** + * Register device tree + * + * @v fdt Device tree header + * @ret rc Return status code + */ +int register_fdt ( const struct fdt_header *hdr ) { + const uint8_t *end; + + /* Record device tree location */ + fdt.hdr = hdr; + fdt.len = be32_to_cpu ( hdr->totalsize ); + DBGC ( &fdt, "FDT version %d at %p+%#04zx\n", + be32_to_cpu ( hdr->version ), fdt.hdr, fdt.len ); + + /* Check signature */ + if ( hdr->magic != cpu_to_be32 ( FDT_MAGIC ) ) { + DBGC ( &fdt, "FDT has invalid magic value %#08x\n", + be32_to_cpu ( hdr->magic ) ); + goto err; + } + + /* Check version */ + if ( hdr->last_comp_version != cpu_to_be32 ( FDT_VERSION ) ) { + DBGC ( &fdt, "FDT unsupported version %d\n", + be32_to_cpu ( hdr->last_comp_version ) ); + goto err; + } + + /* Record structure block location */ + fdt.structure = be32_to_cpu ( hdr->off_dt_struct ); + fdt.structure_len = be32_to_cpu ( hdr->size_dt_struct ); + DBGC ( &fdt, "FDT structure block at +[%#04x,%#04zx)\n", + fdt.structure, ( fdt.structure + fdt.structure_len ) ); + if ( ( fdt.structure > fdt.len ) || + ( fdt.structure_len > ( fdt.len - fdt.structure ) ) ) { + DBGC ( &fdt, "FDT structure block exceeds table\n" ); + goto err; + } + if ( ( fdt.structure | fdt.structure_len ) & + ( FDT_STRUCTURE_ALIGN - 1 ) ) { + DBGC ( &fdt, "FDT structure block is misaligned\n" ); + goto err; + } + + /* Record strings block location */ + fdt.strings = be32_to_cpu ( hdr->off_dt_strings ); + fdt.strings_len = be32_to_cpu ( hdr->size_dt_strings ); + DBGC ( &fdt, "FDT strings block at +[%#04x,%#04zx)\n", + fdt.strings, ( fdt.strings + fdt.strings_len ) ); + if ( ( fdt.strings > fdt.len ) || + ( fdt.strings_len > ( fdt.len - fdt.strings ) ) ) { + DBGC ( &fdt, "FDT strings block exceeds table\n" ); + goto err; + } + + /* Shrink strings block to ensure NUL termination safety */ + end = ( fdt.raw + fdt.strings + fdt.strings_len ); + for ( ; fdt.strings_len ; fdt.strings_len-- ) { + if ( *(--end) == '\0' ) + break; + } + if ( fdt.strings_len != be32_to_cpu ( hdr->size_dt_strings ) ) { + DBGC ( &fdt, "FDT strings block shrunk to +[%#04x,%#04zx)\n", + fdt.strings, ( fdt.strings + fdt.strings_len ) ); + } + + /* Print model name (for debugging) */ + DBGC ( &fdt, "FDT model is \"%s\"\n", fdt_string ( 0, "model" ) ); + + return 0; + + err: + DBGC_HDA ( &fdt, 0, hdr, sizeof ( *hdr ) ); + fdt.hdr = NULL; + return -EINVAL; +} + +/* Drag in objects via register_fdt */ +REQUIRING_SYMBOL ( register_fdt ); + +/* Drag in device tree configuration */ +REQUIRE_OBJECT ( config_fdt ); diff --git a/src/crypto/ocsp.c b/src/crypto/ocsp.c index b83f4c035..2c747fb39 100644 --- a/src/crypto/ocsp.c +++ b/src/crypto/ocsp.c @@ -145,7 +145,7 @@ static void ocsp_free ( struct refcnt *refcnt ) { static int ocsp_request ( struct ocsp_check *ocsp ) { struct digest_algorithm *digest = &ocsp_digest_algorithm; struct asn1_builder *builder = &ocsp->request.builder; - struct asn1_cursor *cert_id = &ocsp->request.cert_id; + struct asn1_cursor *cert_id_tail = &ocsp->request.cert_id_tail; uint8_t digest_ctx[digest->ctxsize]; uint8_t name_digest[digest->digestsize]; uint8_t pubkey_digest[digest->digestsize]; @@ -186,12 +186,14 @@ static int ocsp_request ( struct ocsp_check *ocsp ) { DBGC2_HDA ( ocsp, 0, builder->data, builder->len ); /* Parse certificate ID for comparison with response */ - cert_id->data = builder->data; - cert_id->len = builder->len; - if ( ( rc = ( asn1_enter ( cert_id, ASN1_SEQUENCE ), - asn1_enter ( cert_id, ASN1_SEQUENCE ), - asn1_enter ( cert_id, ASN1_SEQUENCE ), - asn1_enter ( cert_id, ASN1_SEQUENCE ) ) ) != 0 ) { + cert_id_tail->data = builder->data; + cert_id_tail->len = builder->len; + if ( ( rc = ( asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_enter ( cert_id_tail, ASN1_SEQUENCE ), + asn1_skip ( cert_id_tail, ASN1_SEQUENCE ) ) ) != 0 ) { DBGC ( ocsp, "OCSP %p \"%s\" could not locate certID: %s\n", ocsp, x509_name ( ocsp->cert ), strerror ( rc ) ); return rc; @@ -475,15 +477,31 @@ static int ocsp_parse_responder_id ( struct ocsp_check *ocsp, static int ocsp_parse_cert_id ( struct ocsp_check *ocsp, const struct asn1_cursor *raw ) { struct asn1_cursor cursor; + struct asn1_algorithm *algorithm; + int rc; - /* Check certID matches request */ + /* Check certID algorithm */ memcpy ( &cursor, raw, sizeof ( cursor ) ); - asn1_shrink_any ( &cursor ); - if ( asn1_compare ( &cursor, &ocsp->request.cert_id ) != 0 ) { + asn1_enter ( &cursor, ASN1_SEQUENCE ); + if ( ( rc = asn1_digest_algorithm ( &cursor, &algorithm ) ) != 0 ) { + DBGC ( ocsp, "OCSP %p \"%s\" certID unknown algorithm: %s\n", + ocsp, x509_name ( ocsp->cert ), strerror ( rc ) ); + return rc; + } + if ( algorithm->digest != &ocsp_digest_algorithm ) { + DBGC ( ocsp, "OCSP %p \"%s\" certID wrong algorithm %s\n", + ocsp, x509_name ( ocsp->cert ), + algorithm->digest->name ); + return -EACCES_CERT_MISMATCH; + } + + /* Check remaining certID fields */ + asn1_skip ( &cursor, ASN1_SEQUENCE ); + if ( asn1_compare ( &cursor, &ocsp->request.cert_id_tail ) != 0 ) { DBGC ( ocsp, "OCSP %p \"%s\" certID mismatch:\n", ocsp, x509_name ( ocsp->cert ) ); - DBGC_HDA ( ocsp, 0, ocsp->request.cert_id.data, - ocsp->request.cert_id.len ); + DBGC_HDA ( ocsp, 0, ocsp->request.cert_id_tail.data, + ocsp->request.cert_id_tail.len ); DBGC_HDA ( ocsp, 0, cursor.data, cursor.len ); return -EACCES_CERT_MISMATCH; } diff --git a/src/drivers/bus/pcimsix.c b/src/drivers/bus/pcimsix.c new file mode 100644 index 000000000..80893c418 --- /dev/null +++ b/src/drivers/bus/pcimsix.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2019 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 (at your option) 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 ); + +#include <stdint.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/pci.h> +#include <ipxe/pcimsix.h> + +/** @file + * + * PCI MSI-X interrupts + * + */ + +/** + * Get MSI-X descriptor name (for debugging) + * + * @v cfg Configuration space offset + * @ret name Descriptor name + */ +static const char * pci_msix_name ( unsigned int cfg ) { + + switch ( cfg ) { + case PCI_MSIX_DESC_TABLE: return "table"; + case PCI_MSIX_DESC_PBA: return "PBA"; + default: return "<UNKNOWN>"; + } +} + +/** + * Map MSI-X BAR portion + * + * @v pci PCI device + * @v msix MSI-X capability + * @v cfg Configuration space offset + * @ret io I/O address + */ +static void * pci_msix_ioremap ( struct pci_device *pci, struct pci_msix *msix, + unsigned int cfg ) { + uint32_t desc; + unsigned int bar; + unsigned long start; + unsigned long offset; + unsigned long base; + void *io; + + /* Read descriptor */ + pci_read_config_dword ( pci, ( msix->cap + cfg ), &desc ); + + /* Get BAR */ + bar = PCI_MSIX_DESC_BIR ( desc ); + offset = PCI_MSIX_DESC_OFFSET ( desc ); + start = pci_bar_start ( pci, PCI_BASE_ADDRESS ( bar ) ); + if ( ! start ) { + DBGC ( msix, "MSI-X %p %s could not find BAR%d\n", + msix, pci_msix_name ( cfg ), bar ); + return NULL; + } + base = ( start + offset ); + DBGC ( msix, "MSI-X %p %s at %#08lx (BAR%d+%#lx)\n", + msix, pci_msix_name ( cfg ), base, bar, offset ); + + /* Map BAR portion */ + io = ioremap ( ( start + offset ), PCI_MSIX_LEN ); + if ( ! io ) { + DBGC ( msix, "MSI-X %p %s could not map %#08lx\n", + msix, pci_msix_name ( cfg ), base ); + return NULL; + } + + return io; +} + +/** + * Enable MSI-X interrupts + * + * @v pci PCI device + * @v msix MSI-X capability + * @ret rc Return status code + */ +int pci_msix_enable ( struct pci_device *pci, struct pci_msix *msix ) { + uint16_t ctrl; + int rc; + + /* Locate capability */ + msix->cap = pci_find_capability ( pci, PCI_CAP_ID_MSIX ); + if ( ! msix->cap ) { + DBGC ( msix, "MSI-X %p found no MSI-X capability in " + PCI_FMT "\n", msix, PCI_ARGS ( pci ) ); + rc = -ENOENT; + goto err_cap; + } + + /* Extract interrupt count */ + pci_read_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), &ctrl ); + msix->count = ( PCI_MSIX_CTRL_SIZE ( ctrl ) + 1 ); + DBGC ( msix, "MSI-X %p has %d vectors for " PCI_FMT "\n", + msix, msix->count, PCI_ARGS ( pci ) ); + + /* Map MSI-X table */ + msix->table = pci_msix_ioremap ( pci, msix, PCI_MSIX_DESC_TABLE ); + if ( ! msix->table ) { + rc = -ENOENT; + goto err_table; + } + + /* Map pending bit array */ + msix->pba = pci_msix_ioremap ( pci, msix, PCI_MSIX_DESC_PBA ); + if ( ! msix->pba ) { + rc = -ENOENT; + goto err_pba; + } + + /* Enable MSI-X */ + ctrl &= ~PCI_MSIX_CTRL_MASK; + ctrl |= PCI_MSIX_CTRL_ENABLE; + pci_write_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), ctrl ); + + return 0; + + iounmap ( msix->pba ); + err_pba: + iounmap ( msix->table ); + err_table: + err_cap: + return rc; +} + +/** + * Disable MSI-X interrupts + * + * @v pci PCI device + * @v msix MSI-X capability + */ +void pci_msix_disable ( struct pci_device *pci, struct pci_msix *msix ) { + uint16_t ctrl; + + /* Disable MSI-X */ + pci_read_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), &ctrl ); + ctrl &= ~PCI_MSIX_CTRL_ENABLE; + pci_write_config_word ( pci, ( msix->cap + PCI_MSIX_CTRL ), ctrl ); + + /* Unmap pending bit array */ + iounmap ( msix->pba ); + + /* Unmap MSI-X table */ + iounmap ( msix->table ); +} + +/** + * Map MSI-X interrupt vector + * + * @v msix MSI-X capability + * @v vector MSI-X vector + * @v address Message address + * @v data Message data + */ +void pci_msix_map ( struct pci_msix *msix, unsigned int vector, + physaddr_t address, uint32_t data ) { + void *base; + + /* Sanity check */ + assert ( vector < msix->count ); + + /* Map interrupt vector */ + base = ( msix->table + PCI_MSIX_VECTOR ( vector ) ); + writel ( ( address & 0xffffffffUL ), ( base + PCI_MSIX_ADDRESS_LO ) ); + if ( sizeof ( address ) > sizeof ( uint32_t ) ) { + writel ( ( ( ( uint64_t ) address ) >> 32 ), + ( base + PCI_MSIX_ADDRESS_HI ) ); + } else { + writel ( 0, ( base + PCI_MSIX_ADDRESS_HI ) ); + } + writel ( data, ( base + PCI_MSIX_DATA ) ); +} + +/** + * Control MSI-X interrupt vector + * + * @v msix MSI-X capability + * @v vector MSI-X vector + * @v mask Control mask + */ +void pci_msix_control ( struct pci_msix *msix, unsigned int vector, + uint32_t mask ) { + void *base; + uint32_t ctrl; + + /* Mask/unmask interrupt vector */ + base = ( msix->table + PCI_MSIX_VECTOR ( vector ) ); + ctrl = readl ( base + PCI_MSIX_CONTROL ); + ctrl &= ~PCI_MSIX_CONTROL_MASK; + ctrl |= mask; + writel ( ctrl, ( base + PCI_MSIX_CONTROL ) ); +} + +/** + * Dump MSI-X interrupt state (for debugging) + * + * @v msix MSI-X capability + * @v vector MSI-X vector + */ +void pci_msix_dump ( struct pci_msix *msix, unsigned int vector ) { + void *base; + uint32_t address_hi; + uint32_t address_lo; + physaddr_t address; + uint32_t data; + uint32_t ctrl; + uint32_t pba; + + /* Do nothing in non-debug builds */ + if ( ! DBG_LOG ) + return; + + /* Mask/unmask interrupt vector */ + base = ( msix->table + PCI_MSIX_VECTOR ( vector ) ); + address_hi = readl ( base + PCI_MSIX_ADDRESS_HI ); + address_lo = readl ( base + PCI_MSIX_ADDRESS_LO ); + data = readl ( base + PCI_MSIX_DATA ); + ctrl = readl ( base + PCI_MSIX_CONTROL ); + pba = readl ( msix->pba ); + address = ( ( ( ( uint64_t ) address_hi ) << 32 ) | address_lo ); + DBGC ( msix, "MSI-X %p vector %d %#08x => %#08lx%s%s\n", + msix, vector, data, address, + ( ( ctrl & PCI_MSIX_CONTROL_MASK ) ? " (masked)" : "" ), + ( ( pba & ( 1 << vector ) ) ? " (pending)" : "" ) ); +} diff --git a/src/drivers/infiniband/golan.c b/src/drivers/infiniband/golan.c index 18ebfb1e9..e96ba2698 100755 --- a/src/drivers/infiniband/golan.c +++ b/src/drivers/infiniband/golan.c @@ -2640,6 +2640,9 @@ static struct pci_device_id golan_nics[] = { PCI_ROM ( 0x15b3, 0x1015, "ConnectX-4Lx", "ConnectX-4Lx HCA driver, DevID 4117", 0 ), PCI_ROM ( 0x15b3, 0x1017, "ConnectX-5", "ConnectX-5 HCA driver, DevID 4119", 0 ), PCI_ROM ( 0x15b3, 0x1019, "ConnectX-5EX", "ConnectX-5EX HCA driver, DevID 4121", 0 ), + PCI_ROM ( 0x15b3, 0x101b, "ConnectX-6", "ConnectX-6 HCA driver, DevID 4123", 0 ), + PCI_ROM ( 0x15b3, 0x101d, "ConnectX-6DX", "ConnectX-6DX HCA driver, DevID 4125", 0 ), + PCI_ROM ( 0x15b3, 0xa2d2, "BlueField", "BlueField integrated ConnectX-5 network controller HCA driver, DevID 41682", 0 ), }; struct pci_driver golan_driver __pci_driver = { diff --git a/src/drivers/infiniband/hermon.c b/src/drivers/infiniband/hermon.c index a1d2a3bd5..9675c156b 100644 --- a/src/drivers/infiniband/hermon.c +++ b/src/drivers/infiniband/hermon.c @@ -3207,22 +3207,16 @@ static void hermon_eth_complete_recv ( struct ib_device *ibdev __unused, struct ib_address_vector *source, struct io_buffer *iobuf, int rc ) { struct net_device *netdev = ib_qp_get_ownerdata ( qp ); - struct net_device *vlan; - - /* Find VLAN device, if applicable */ - if ( source->vlan_present ) { - if ( ( vlan = vlan_find ( netdev, source->vlan ) ) != NULL ) { - netdev = vlan; - } else if ( rc == 0 ) { - rc = -ENODEV; - } - } + unsigned int tag; + + /* Identify VLAN tag, if applicable */ + tag = ( source->vlan_present ? source->vlan : 0 ); /* Hand off to network layer */ if ( rc == 0 ) { - netdev_rx ( netdev, iobuf ); + vlan_netdev_rx ( netdev, tag, iobuf ); } else { - netdev_rx_err ( netdev, iobuf, rc ); + vlan_netdev_rx_err ( netdev, tag, iobuf, rc ); } } diff --git a/src/drivers/net/intel.c b/src/drivers/net/intel.c index a2e6d535b..bb0b673b9 100644 --- a/src/drivers/net/intel.c +++ b/src/drivers/net/intel.c @@ -1146,9 +1146,17 @@ static struct pci_device_id intel_nics[] = { PCI_ROM ( 0x8086, 0x15b7, "i219lm-2", "I219-LM (2)", INTEL_I219 ), PCI_ROM ( 0x8086, 0x15b8, "i219v-2", "I219-V (2)", INTEL_I219 ), PCI_ROM ( 0x8086, 0x15b9, "i219lm-3", "I219-LM (3)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15bb, "i219lm-7", "I219-LM (7)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15bc, "i219v-7", "I219-V (7)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15bd, "i219lm-6", "I219-LM (6)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15be, "i219v-6", "I219-V (6)", INTEL_I219 ), PCI_ROM ( 0x8086, 0x15d6, "i219v-5", "I219-V (5)", INTEL_I219 ), PCI_ROM ( 0x8086, 0x15d7, "i219lm-4", "I219-LM (4)", INTEL_I219 ), PCI_ROM ( 0x8086, 0x15d8, "i219v-4", "I219-V (4)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15df, "i219lm-8", "I219-LM (8)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15e0, "i219v-8", "I219-V (8)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15e1, "i219lm-9", "I219-LM (9)", INTEL_I219 ), + PCI_ROM ( 0x8086, 0x15e2, "i219v-9", "I219-V (9)", INTEL_I219 ), PCI_ROM ( 0x8086, 0x15e3, "i219lm-5", "I219-LM (5)", INTEL_I219 ), PCI_ROM ( 0x8086, 0x1f41, "i354", "I354", INTEL_NO_ASDE ), PCI_ROM ( 0x8086, 0x294c, "82566dc-2", "82566DC-2", 0 ), diff --git a/src/drivers/net/intelxl.c b/src/drivers/net/intelxl.c index 3e40fa4ae..c98ba265c 100644 --- a/src/drivers/net/intelxl.c +++ b/src/drivers/net/intelxl.c @@ -32,6 +32,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/netdevice.h> #include <ipxe/ethernet.h> #include <ipxe/if_ether.h> +#include <ipxe/vlan.h> #include <ipxe/iobuf.h> #include <ipxe/malloc.h> #include <ipxe/pci.h> @@ -117,98 +118,239 @@ static int intelxl_fetch_mac ( struct intelxl_nic *intelxl, /****************************************************************************** * + * MSI-X interrupts + * + ****************************************************************************** + */ + +/** + * Enable MSI-X dummy interrupt + * + * @v intelxl Intel device + * @v pci PCI device + * @ret rc Return status code + */ +int intelxl_msix_enable ( struct intelxl_nic *intelxl, + struct pci_device *pci ) { + int rc; + + /* Enable MSI-X capability */ + if ( ( rc = pci_msix_enable ( pci, &intelxl->msix ) ) != 0 ) { + DBGC ( intelxl, "INTELXL %p could not enable MSI-X: %s\n", + intelxl, strerror ( rc ) ); + return rc; + } + + /* Configure interrupt zero to write to dummy location */ + pci_msix_map ( &intelxl->msix, 0, virt_to_bus ( &intelxl->msg ), 0 ); + + /* Enable dummy interrupt zero */ + pci_msix_unmask ( &intelxl->msix, 0 ); + + return 0; +} + +/** + * Disable MSI-X dummy interrupt + * + * @v intelxl Intel device + * @v pci PCI device + */ +void intelxl_msix_disable ( struct intelxl_nic *intelxl, + struct pci_device *pci ) { + + /* Disable dummy interrupt zero */ + pci_msix_mask ( &intelxl->msix, 0 ); + + /* Disable MSI-X capability */ + pci_msix_disable ( pci, &intelxl->msix ); +} + +/****************************************************************************** + * * Admin queue * ****************************************************************************** */ +/** Admin queue register offsets */ +static const struct intelxl_admin_offsets intelxl_admin_offsets = { + .bal = INTELXL_ADMIN_BAL, + .bah = INTELXL_ADMIN_BAH, + .len = INTELXL_ADMIN_LEN, + .head = INTELXL_ADMIN_HEAD, + .tail = INTELXL_ADMIN_TAIL, +}; + /** - * Create admin queue + * Allocate admin queue * * @v intelxl Intel device * @v admin Admin queue * @ret rc Return status code */ -static int intelxl_create_admin ( struct intelxl_nic *intelxl, - struct intelxl_admin *admin ) { +static int intelxl_alloc_admin ( struct intelxl_nic *intelxl, + struct intelxl_admin *admin ) { + size_t buf_len = ( sizeof ( admin->buf[0] ) * INTELXL_ADMIN_NUM_DESC ); size_t len = ( sizeof ( admin->desc[0] ) * INTELXL_ADMIN_NUM_DESC ); - void *admin_regs = ( intelxl->regs + admin->reg ); - physaddr_t address; /* Allocate admin queue */ - admin->desc = malloc_dma ( ( len + sizeof ( *admin->buffer ) ), - INTELXL_ALIGN ); - if ( ! admin->desc ) + admin->buf = malloc_dma ( ( buf_len + len ), INTELXL_ALIGN ); + if ( ! admin->buf ) return -ENOMEM; - admin->buffer = ( ( ( void * ) admin->desc ) + len ); + admin->desc = ( ( ( void * ) admin->buf ) + buf_len ); + + DBGC ( intelxl, "INTELXL %p A%cQ is at [%08llx,%08llx) buf " + "[%08llx,%08llx)\n", intelxl, + ( ( admin == &intelxl->command ) ? 'T' : 'R' ), + ( ( unsigned long long ) virt_to_bus ( admin->desc ) ), + ( ( unsigned long long ) ( virt_to_bus ( admin->desc ) + len ) ), + ( ( unsigned long long ) virt_to_bus ( admin->buf ) ), + ( ( unsigned long long ) ( virt_to_bus ( admin->buf ) + + buf_len ) ) ); + return 0; +} + +/** + * Enable admin queue + * + * @v intelxl Intel device + * @v admin Admin queue + */ +static void intelxl_enable_admin ( struct intelxl_nic *intelxl, + struct intelxl_admin *admin ) { + size_t len = ( sizeof ( admin->desc[0] ) * INTELXL_ADMIN_NUM_DESC ); + const struct intelxl_admin_offsets *regs = admin->regs; + void *admin_regs = ( intelxl->regs + admin->base ); + physaddr_t address; /* Initialise admin queue */ memset ( admin->desc, 0, len ); /* Reset head and tail registers */ - writel ( 0, admin_regs + INTELXL_ADMIN_HEAD ); - writel ( 0, admin_regs + INTELXL_ADMIN_TAIL ); + writel ( 0, admin_regs + regs->head ); + writel ( 0, admin_regs + regs->tail ); /* Reset queue index */ admin->index = 0; /* Program queue address */ address = virt_to_bus ( admin->desc ); - writel ( ( address & 0xffffffffUL ), admin_regs + INTELXL_ADMIN_BAL ); + writel ( ( address & 0xffffffffUL ), admin_regs + regs->bal ); if ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) ) { writel ( ( ( ( uint64_t ) address ) >> 32 ), - admin_regs + INTELXL_ADMIN_BAH ); + admin_regs + regs->bah ); } else { - writel ( 0, admin_regs + INTELXL_ADMIN_BAH ); + writel ( 0, admin_regs + regs->bah ); } /* Program queue length and enable queue */ writel ( ( INTELXL_ADMIN_LEN_LEN ( INTELXL_ADMIN_NUM_DESC ) | INTELXL_ADMIN_LEN_ENABLE ), - admin_regs + INTELXL_ADMIN_LEN ); - - DBGC ( intelxl, "INTELXL %p A%cQ is at [%08llx,%08llx) buf " - "[%08llx,%08llx)\n", intelxl, - ( ( admin->reg == INTELXL_ADMIN_CMD ) ? 'T' : 'R' ), - ( ( unsigned long long ) address ), - ( ( unsigned long long ) address + len ), - ( ( unsigned long long ) virt_to_bus ( admin->buffer ) ), - ( ( unsigned long long ) ( virt_to_bus ( admin->buffer ) + - sizeof ( admin->buffer[0] ) ) ) ); - return 0; + admin_regs + regs->len ); } /** - * Destroy admin queue + * Disable admin queue * * @v intelxl Intel device * @v admin Admin queue */ -static void intelxl_destroy_admin ( struct intelxl_nic *intelxl, +static void intelxl_disable_admin ( struct intelxl_nic *intelxl, struct intelxl_admin *admin ) { - size_t len = ( sizeof ( admin->desc[0] ) * INTELXL_ADMIN_NUM_DESC ); - void *admin_regs = ( intelxl->regs + admin->reg ); + const struct intelxl_admin_offsets *regs = admin->regs; + void *admin_regs = ( intelxl->regs + admin->base ); /* Disable queue */ - writel ( 0, admin_regs + INTELXL_ADMIN_LEN ); + writel ( 0, admin_regs + regs->len ); +} + +/** + * Free admin queue + * + * @v intelxl Intel device + * @v admin Admin queue + */ +static void intelxl_free_admin ( struct intelxl_nic *intelxl __unused, + struct intelxl_admin *admin ) { + size_t buf_len = ( sizeof ( admin->buf[0] ) * INTELXL_ADMIN_NUM_DESC ); + size_t len = ( sizeof ( admin->desc[0] ) * INTELXL_ADMIN_NUM_DESC ); /* Free queue */ - free_dma ( admin->desc, ( len + sizeof ( *admin->buffer ) ) ); + free_dma ( admin->buf, ( buf_len + len ) ); +} + +/** + * Get next admin command queue descriptor + * + * @v intelxl Intel device + * @ret cmd Command descriptor + */ +struct intelxl_admin_descriptor * +intelxl_admin_command_descriptor ( struct intelxl_nic *intelxl ) { + struct intelxl_admin *admin = &intelxl->command; + struct intelxl_admin_descriptor *cmd; + + /* Get and initialise next descriptor */ + cmd = &admin->desc[ admin->index % INTELXL_ADMIN_NUM_DESC ]; + memset ( cmd, 0, sizeof ( *cmd ) ); + return cmd; +} + +/** + * Get next admin command queue data buffer + * + * @v intelxl Intel device + * @ret buf Data buffer + */ +union intelxl_admin_buffer * +intelxl_admin_command_buffer ( struct intelxl_nic *intelxl ) { + struct intelxl_admin *admin = &intelxl->command; + union intelxl_admin_buffer *buf; + + /* Get next data buffer */ + buf = &admin->buf[ admin->index % INTELXL_ADMIN_NUM_DESC ]; + memset ( buf, 0, sizeof ( *buf ) ); + return buf; +} + +/** + * Initialise admin event queue descriptor + * + * @v intelxl Intel device + * @v index Event queue index + */ +static void intelxl_admin_event_init ( struct intelxl_nic *intelxl, + unsigned int index ) { + struct intelxl_admin *admin = &intelxl->event; + struct intelxl_admin_descriptor *evt; + union intelxl_admin_buffer *buf; + uint64_t address; + + /* Initialise descriptor */ + evt = &admin->desc[ index % INTELXL_ADMIN_NUM_DESC ]; + buf = &admin->buf[ index % INTELXL_ADMIN_NUM_DESC ]; + address = virt_to_bus ( buf ); + evt->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ); + evt->len = cpu_to_le16 ( sizeof ( *buf ) ); + evt->params.buffer.high = cpu_to_le32 ( address >> 32 ); + evt->params.buffer.low = cpu_to_le32 ( address & 0xffffffffUL ); } /** * Issue admin queue command * * @v intelxl Intel device - * @v cmd Command descriptor * @ret rc Return status code */ -static int intelxl_admin_command ( struct intelxl_nic *intelxl, - struct intelxl_admin_descriptor *cmd ) { +int intelxl_admin_command ( struct intelxl_nic *intelxl ) { struct intelxl_admin *admin = &intelxl->command; - void *admin_regs = ( intelxl->regs + admin->reg ); - struct intelxl_admin_descriptor *desc; - uint64_t buffer; + const struct intelxl_admin_offsets *regs = admin->regs; + void *admin_regs = ( intelxl->regs + admin->base ); + struct intelxl_admin_descriptor *cmd; + union intelxl_admin_buffer *buf; + uint64_t address; + uint32_t cookie; unsigned int index; unsigned int tail; unsigned int i; @@ -217,69 +359,74 @@ static int intelxl_admin_command ( struct intelxl_nic *intelxl, /* Get next queue entry */ index = admin->index++; tail = ( admin->index % INTELXL_ADMIN_NUM_DESC ); - desc = &admin->desc[index % INTELXL_ADMIN_NUM_DESC]; - - /* Clear must-be-zero flags */ - cmd->flags &= ~cpu_to_le16 ( INTELXL_ADMIN_FL_DD | - INTELXL_ADMIN_FL_CMP | - INTELXL_ADMIN_FL_ERR ); - - /* Clear return value */ - cmd->ret = 0; - - /* Populate cookie */ - cmd->cookie = cpu_to_le32 ( index ); + cmd = &admin->desc[ index % INTELXL_ADMIN_NUM_DESC ]; + buf = &admin->buf[ index % INTELXL_ADMIN_NUM_DESC ]; + DBGC2 ( intelxl, "INTELXL %p admin command %#x opcode %#04x", + intelxl, index, le16_to_cpu ( cmd->opcode ) ); + if ( cmd->vopcode ) + DBGC2 ( intelxl, "/%#08x", le32_to_cpu ( cmd->vopcode ) ); + DBGC2 ( intelxl, ":\n" ); + + /* Sanity checks */ + assert ( ! ( cmd->flags & cpu_to_le16 ( INTELXL_ADMIN_FL_DD ) ) ); + assert ( ! ( cmd->flags & cpu_to_le16 ( INTELXL_ADMIN_FL_CMP ) ) ); + assert ( ! ( cmd->flags & cpu_to_le16 ( INTELXL_ADMIN_FL_ERR ) ) ); + assert ( cmd->ret == 0 ); /* Populate data buffer address if applicable */ if ( cmd->flags & cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ) ) { - buffer = virt_to_bus ( admin->buffer ); - cmd->params.buffer.high = cpu_to_le32 ( buffer >> 32 ); - cmd->params.buffer.low = cpu_to_le32 ( buffer & 0xffffffffUL ); + address = virt_to_bus ( buf ); + cmd->params.buffer.high = cpu_to_le32 ( address >> 32 ); + cmd->params.buffer.low = cpu_to_le32 ( address & 0xffffffffUL ); } - /* Copy command descriptor to queue entry */ - memcpy ( desc, cmd, sizeof ( *desc ) ); - DBGC2 ( intelxl, "INTELXL %p admin command %#x:\n", intelxl, index ); - DBGC2_HDA ( intelxl, virt_to_phys ( desc ), desc, sizeof ( *desc ) ); + /* Populate cookie, if not being (ab)used for VF opcode */ + if ( ! cmd->vopcode ) + cmd->cookie = cpu_to_le32 ( index ); + + /* Record cookie */ + cookie = cmd->cookie; /* Post command descriptor */ + DBGC2_HDA ( intelxl, virt_to_phys ( cmd ), cmd, sizeof ( *cmd ) ); + if ( cmd->flags & cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ) ) { + DBGC2_HDA ( intelxl, virt_to_phys ( buf ), buf, + le16_to_cpu ( cmd->len ) ); + } wmb(); - writel ( tail, admin_regs + INTELXL_ADMIN_TAIL ); + writel ( tail, admin_regs + regs->tail ); /* Wait for completion */ for ( i = 0 ; i < INTELXL_ADMIN_MAX_WAIT_MS ; i++ ) { /* If response is not complete, delay 1ms and retry */ - if ( ! ( desc->flags & INTELXL_ADMIN_FL_DD ) ) { + if ( ! ( cmd->flags & INTELXL_ADMIN_FL_DD ) ) { mdelay ( 1 ); continue; } DBGC2 ( intelxl, "INTELXL %p admin command %#x response:\n", intelxl, index ); - DBGC2_HDA ( intelxl, virt_to_phys ( desc ), desc, - sizeof ( *desc ) ); + DBGC2_HDA ( intelxl, virt_to_phys ( cmd ), cmd, + sizeof ( *cmd ) ); /* Check for cookie mismatch */ - if ( desc->cookie != cmd->cookie ) { + if ( cmd->cookie != cookie ) { DBGC ( intelxl, "INTELXL %p admin command %#x bad " "cookie %#x\n", intelxl, index, - le32_to_cpu ( desc->cookie ) ); + le32_to_cpu ( cmd->cookie ) ); rc = -EPROTO; goto err; } /* Check for errors */ - if ( desc->ret != 0 ) { + if ( cmd->ret != 0 ) { DBGC ( intelxl, "INTELXL %p admin command %#x error " "%d\n", intelxl, index, - le16_to_cpu ( desc->ret ) ); + le16_to_cpu ( cmd->ret ) ); rc = -EIO; goto err; } - /* Copy response back to command descriptor */ - memcpy ( cmd, desc, sizeof ( *cmd ) ); - /* Success */ return 0; } @@ -288,8 +435,7 @@ static int intelxl_admin_command ( struct intelxl_nic *intelxl, DBGC ( intelxl, "INTELXL %p timed out waiting for admin command %#x:\n", intelxl, index ); err: - DBGC_HDA ( intelxl, virt_to_phys ( desc ), cmd, sizeof ( *cmd ) ); - DBGC_HDA ( intelxl, virt_to_phys ( desc ), desc, sizeof ( *desc ) ); + DBGC_HDA ( intelxl, virt_to_phys ( cmd ), cmd, sizeof ( *cmd ) ); return rc; } @@ -300,17 +446,18 @@ static int intelxl_admin_command ( struct intelxl_nic *intelxl, * @ret rc Return status code */ static int intelxl_admin_version ( struct intelxl_nic *intelxl ) { - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_version_params *version = &cmd.params.version; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_version_params *version; unsigned int api; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_VERSION ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_VERSION ); + version = &cmd->params.version; /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; api = le16_to_cpu ( version->api.major ); DBGC ( intelxl, "INTELXL %p firmware v%d.%d API v%d.%d\n", @@ -335,24 +482,25 @@ static int intelxl_admin_version ( struct intelxl_nic *intelxl ) { * @ret rc Return status code */ static int intelxl_admin_driver ( struct intelxl_nic *intelxl ) { - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_driver_params *driver = &cmd.params.driver; - struct intelxl_admin_driver_buffer *buf = - &intelxl->command.buffer->driver; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_driver_params *driver; + union intelxl_admin_buffer *buf; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_DRIVER ); - cmd.flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); - cmd.len = cpu_to_le16 ( sizeof ( *buf ) ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_DRIVER ); + cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); + cmd->len = cpu_to_le16 ( sizeof ( buf->driver ) ); + driver = &cmd->params.driver; driver->major = product_major_version; driver->minor = product_minor_version; - snprintf ( buf->name, sizeof ( buf->name ), "%s", + buf = intelxl_admin_command_buffer ( intelxl ); + snprintf ( buf->driver.name, sizeof ( buf->driver.name ), "%s", ( product_name[0] ? product_name : product_short_name ) ); /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; return 0; @@ -365,17 +513,51 @@ static int intelxl_admin_driver ( struct intelxl_nic *intelxl ) { * @ret rc Return status code */ static int intelxl_admin_shutdown ( struct intelxl_nic *intelxl ) { - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_shutdown_params *shutdown = &cmd.params.shutdown; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_shutdown_params *shutdown; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_SHUTDOWN ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_SHUTDOWN ); + shutdown = &cmd->params.shutdown; shutdown->unloading = INTELXL_ADMIN_SHUTDOWN_UNLOADING; /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Clear PXE mode + * + * @v intelxl Intel device + * @ret rc Return status code + */ +static int intelxl_admin_clear_pxe ( struct intelxl_nic *intelxl ) { + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_clear_pxe_params *pxe; + uint32_t gllan_rctl_0; + int rc; + + /* Do nothing if device is already out of PXE mode */ + gllan_rctl_0 = readl ( intelxl->regs + INTELXL_GLLAN_RCTL_0 ); + if ( ! ( gllan_rctl_0 & INTELXL_GLLAN_RCTL_0_PXE_MODE ) ) { + DBGC2 ( intelxl, "INTELXL %p already in non-PXE mode\n", + intelxl ); + return 0; + } + + /* Populate descriptor */ + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_CLEAR_PXE ); + pxe = &cmd->params.pxe; + pxe->magic = INTELXL_ADMIN_CLEAR_PXE_MAGIC; + + /* Issue command */ + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; return 0; @@ -388,36 +570,38 @@ static int intelxl_admin_shutdown ( struct intelxl_nic *intelxl ) { * @ret rc Return status code */ static int intelxl_admin_switch ( struct intelxl_nic *intelxl ) { - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_switch_params *sw = &cmd.params.sw; - struct intelxl_admin_switch_buffer *buf = &intelxl->command.buffer->sw; - struct intelxl_admin_switch_config *cfg = &buf->cfg; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_switch_params *sw; + union intelxl_admin_buffer *buf; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_SWITCH ); - cmd.flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ); - cmd.len = cpu_to_le16 ( sizeof ( *buf ) ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_SWITCH ); + cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ); + cmd->len = cpu_to_le16 ( sizeof ( buf->sw ) ); + sw = &cmd->params.sw; + buf = intelxl_admin_command_buffer ( intelxl ); /* Get each configuration in turn */ do { /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; /* Dump raw configuration */ DBGC2 ( intelxl, "INTELXL %p SEID %#04x:\n", - intelxl, le16_to_cpu ( cfg->seid ) ); - DBGC2_HDA ( intelxl, 0, cfg, sizeof ( *cfg ) ); + intelxl, le16_to_cpu ( buf->sw.cfg.seid ) ); + DBGC2_HDA ( intelxl, 0, &buf->sw.cfg, sizeof ( buf->sw.cfg ) ); /* Parse response */ - if ( cfg->type == INTELXL_ADMIN_SWITCH_TYPE_VSI ) { - intelxl->vsi = le16_to_cpu ( cfg->seid ); + if ( buf->sw.cfg.type == INTELXL_ADMIN_SWITCH_TYPE_VSI ) { + intelxl->vsi = le16_to_cpu ( buf->sw.cfg.seid ); DBGC ( intelxl, "INTELXL %p VSI %#04x uplink %#04x " "downlink %#04x conn %#02x\n", intelxl, - intelxl->vsi, le16_to_cpu ( cfg->uplink ), - le16_to_cpu ( cfg->downlink ), cfg->connection ); + intelxl->vsi, le16_to_cpu ( buf->sw.cfg.uplink ), + le16_to_cpu ( buf->sw.cfg.downlink ), + buf->sw.cfg.connection ); } } while ( sw->next ); @@ -438,25 +622,27 @@ static int intelxl_admin_switch ( struct intelxl_nic *intelxl ) { * @ret rc Return status code */ static int intelxl_admin_vsi ( struct intelxl_nic *intelxl ) { - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_vsi_params *vsi = &cmd.params.vsi; - struct intelxl_admin_vsi_buffer *buf = &intelxl->command.buffer->vsi; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_vsi_params *vsi; + union intelxl_admin_buffer *buf; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_VSI ); - cmd.flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ); - cmd.len = cpu_to_le16 ( sizeof ( *buf ) ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_VSI ); + cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ); + cmd->len = cpu_to_le16 ( sizeof ( buf->vsi ) ); + vsi = &cmd->params.vsi; vsi->vsi = cpu_to_le16 ( intelxl->vsi ); + buf = intelxl_admin_command_buffer ( intelxl ); /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; /* Parse response */ - intelxl->queue = le16_to_cpu ( buf->queue[0] ); - intelxl->qset = le16_to_cpu ( buf->qset[0] ); + intelxl->queue = le16_to_cpu ( buf->vsi.queue[0] ); + intelxl->qset = le16_to_cpu ( buf->vsi.qset[0] ); DBGC ( intelxl, "INTELXL %p VSI %#04x queue %#04x qset %#04x\n", intelxl, intelxl->vsi, intelxl->queue, intelxl->qset ); @@ -470,24 +656,25 @@ static int intelxl_admin_vsi ( struct intelxl_nic *intelxl ) { * @ret rc Return status code */ static int intelxl_admin_promisc ( struct intelxl_nic *intelxl ) { - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_promisc_params *promisc = &cmd.params.promisc; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_promisc_params *promisc; uint16_t flags; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_PROMISC ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_PROMISC ); flags = ( INTELXL_ADMIN_PROMISC_FL_UNICAST | INTELXL_ADMIN_PROMISC_FL_MULTICAST | INTELXL_ADMIN_PROMISC_FL_BROADCAST | INTELXL_ADMIN_PROMISC_FL_VLAN ); + promisc = &cmd->params.promisc; promisc->flags = cpu_to_le16 ( flags ); promisc->valid = cpu_to_le16 ( flags ); promisc->vsi = cpu_to_le16 ( intelxl->vsi ); /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; return 0; @@ -500,18 +687,19 @@ static int intelxl_admin_promisc ( struct intelxl_nic *intelxl ) { * @ret rc Return status code */ static int intelxl_admin_autoneg ( struct intelxl_nic *intelxl ) { - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_autoneg_params *autoneg = &cmd.params.autoneg; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_autoneg_params *autoneg; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_AUTONEG ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_AUTONEG ); + autoneg = &cmd->params.autoneg; autoneg->flags = ( INTELXL_ADMIN_AUTONEG_FL_RESTART | INTELXL_ADMIN_AUTONEG_FL_ENABLE ); /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; return 0; @@ -525,17 +713,18 @@ static int intelxl_admin_autoneg ( struct intelxl_nic *intelxl ) { */ static int intelxl_admin_link ( struct net_device *netdev ) { struct intelxl_nic *intelxl = netdev->priv; - struct intelxl_admin_descriptor cmd; - struct intelxl_admin_link_params *link = &cmd.params.link; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_link_params *link; int rc; /* Populate descriptor */ - memset ( &cmd, 0, sizeof ( cmd ) ); - cmd.opcode = cpu_to_le16 ( INTELXL_ADMIN_LINK ); + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_LINK ); + link = &cmd->params.link; link->notify = INTELXL_ADMIN_LINK_NOTIFY; /* Issue command */ - if ( ( rc = intelxl_admin_command ( intelxl, &cmd ) ) != 0 ) + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) return rc; DBGC ( intelxl, "INTELXL %p PHY %#02x speed %#02x status %#02x\n", intelxl, link->phy, link->speed, link->status ); @@ -551,19 +740,36 @@ static int intelxl_admin_link ( struct net_device *netdev ) { } /** + * Handle virtual function event (when VF driver is not present) + * + * @v netdev Network device + * @v evt Admin queue event descriptor + * @v buf Admin queue event data buffer + */ +__weak void +intelxlvf_admin_event ( struct net_device *netdev __unused, + struct intelxl_admin_descriptor *evt __unused, + union intelxl_admin_buffer *buf __unused ) { + + /* Nothing to do */ +} + +/** * Refill admin event queue * * @v intelxl Intel device */ static void intelxl_refill_admin ( struct intelxl_nic *intelxl ) { struct intelxl_admin *admin = &intelxl->event; - void *admin_regs = ( intelxl->regs + admin->reg ); + const struct intelxl_admin_offsets *regs = admin->regs; + void *admin_regs = ( intelxl->regs + admin->base ); unsigned int tail; /* Update tail pointer */ tail = ( ( admin->index + INTELXL_ADMIN_NUM_DESC - 1 ) % INTELXL_ADMIN_NUM_DESC ); - writel ( tail, admin_regs + INTELXL_ADMIN_TAIL ); + wmb(); + writel ( tail, admin_regs + regs->tail ); } /** @@ -571,42 +777,48 @@ static void intelxl_refill_admin ( struct intelxl_nic *intelxl ) { * * @v netdev Network device */ -static void intelxl_poll_admin ( struct net_device *netdev ) { +void intelxl_poll_admin ( struct net_device *netdev ) { struct intelxl_nic *intelxl = netdev->priv; struct intelxl_admin *admin = &intelxl->event; - struct intelxl_admin_descriptor *desc; + struct intelxl_admin_descriptor *evt; + union intelxl_admin_buffer *buf; /* Check for events */ while ( 1 ) { - /* Get next event descriptor */ - desc = &admin->desc[admin->index % INTELXL_ADMIN_NUM_DESC]; + /* Get next event descriptor and data buffer */ + evt = &admin->desc[ admin->index % INTELXL_ADMIN_NUM_DESC ]; + buf = &admin->buf[ admin->index % INTELXL_ADMIN_NUM_DESC ]; /* Stop if descriptor is not yet completed */ - if ( ! ( desc->flags & INTELXL_ADMIN_FL_DD ) ) + if ( ! ( evt->flags & INTELXL_ADMIN_FL_DD ) ) return; DBGC2 ( intelxl, "INTELXL %p admin event %#x:\n", intelxl, admin->index ); - DBGC2_HDA ( intelxl, virt_to_phys ( desc ), desc, - sizeof ( *desc ) ); + DBGC2_HDA ( intelxl, virt_to_phys ( evt ), evt, + sizeof ( *evt ) ); + if ( evt->flags & cpu_to_le16 ( INTELXL_ADMIN_FL_BUF ) ) { + DBGC2_HDA ( intelxl, virt_to_phys ( buf ), buf, + le16_to_cpu ( evt->len ) ); + } /* Handle event */ - switch ( desc->opcode ) { + switch ( evt->opcode ) { case cpu_to_le16 ( INTELXL_ADMIN_LINK ): intelxl_admin_link ( netdev ); break; + case cpu_to_le16 ( INTELXL_ADMIN_SEND_TO_VF ): + intelxlvf_admin_event ( netdev, evt, buf ); + break; default: DBGC ( intelxl, "INTELXL %p admin event %#x " "unrecognised opcode %#04x\n", intelxl, - admin->index, le16_to_cpu ( desc->opcode ) ); + admin->index, le16_to_cpu ( evt->opcode ) ); break; } - /* Clear event completion flag */ - desc->flags = 0; - wmb(); - - /* Update index and refill queue */ + /* Reset descriptor and refill queue */ + intelxl_admin_event_init ( intelxl, admin->index ); admin->index++; intelxl_refill_admin ( intelxl ); } @@ -618,19 +830,19 @@ static void intelxl_poll_admin ( struct net_device *netdev ) { * @v intelxl Intel device * @ret rc Return status code */ -static int intelxl_open_admin ( struct intelxl_nic *intelxl ) { +int intelxl_open_admin ( struct intelxl_nic *intelxl ) { int rc; - /* Create admin event queue */ - if ( ( rc = intelxl_create_admin ( intelxl, &intelxl->event ) ) != 0 ) - goto err_create_event; + /* Allocate admin event queue */ + if ( ( rc = intelxl_alloc_admin ( intelxl, &intelxl->event ) ) != 0 ) + goto err_alloc_event; - /* Create admin command queue */ - if ( ( rc = intelxl_create_admin ( intelxl, &intelxl->command ) ) != 0 ) - goto err_create_command; + /* Allocate admin command queue */ + if ( ( rc = intelxl_alloc_admin ( intelxl, &intelxl->command ) ) != 0 ) + goto err_alloc_command; - /* Post all descriptors to event queue */ - intelxl_refill_admin ( intelxl ); + /* (Re)open admin queues */ + intelxl_reopen_admin ( intelxl ); /* Get firmware version */ if ( ( rc = intelxl_admin_version ( intelxl ) ) != 0 ) @@ -644,28 +856,54 @@ static int intelxl_open_admin ( struct intelxl_nic *intelxl ) { err_driver: err_version: - intelxl_destroy_admin ( intelxl, &intelxl->command ); - err_create_command: - intelxl_destroy_admin ( intelxl, &intelxl->event ); - err_create_event: + intelxl_disable_admin ( intelxl, &intelxl->command ); + intelxl_disable_admin ( intelxl, &intelxl->event ); + intelxl_free_admin ( intelxl, &intelxl->command ); + err_alloc_command: + intelxl_free_admin ( intelxl, &intelxl->event ); + err_alloc_event: return rc; } /** + * Reopen admin queues (after virtual function reset) + * + * @v intelxl Intel device + */ +void intelxl_reopen_admin ( struct intelxl_nic *intelxl ) { + unsigned int i; + + /* Enable admin event queue */ + intelxl_enable_admin ( intelxl, &intelxl->event ); + + /* Enable admin command queue */ + intelxl_enable_admin ( intelxl, &intelxl->command ); + + /* Initialise all admin event queue descriptors */ + for ( i = 0 ; i < INTELXL_ADMIN_NUM_DESC ; i++ ) + intelxl_admin_event_init ( intelxl, i ); + + /* Post all descriptors to event queue */ + intelxl_refill_admin ( intelxl ); +} + +/** * Close admin queues * * @v intelxl Intel device */ -static void intelxl_close_admin ( struct intelxl_nic *intelxl ) { +void intelxl_close_admin ( struct intelxl_nic *intelxl ) { /* Shut down admin queues */ intelxl_admin_shutdown ( intelxl ); - /* Destroy admin command queue */ - intelxl_destroy_admin ( intelxl, &intelxl->command ); + /* Disable admin queues */ + intelxl_disable_admin ( intelxl, &intelxl->command ); + intelxl_disable_admin ( intelxl, &intelxl->event ); - /* Destroy admin event queue */ - intelxl_destroy_admin ( intelxl, &intelxl->event ); + /* Free admin queues */ + intelxl_free_admin ( intelxl, &intelxl->command ); + intelxl_free_admin ( intelxl, &intelxl->event ); } /****************************************************************************** @@ -676,6 +914,62 @@ static void intelxl_close_admin ( struct intelxl_nic *intelxl ) { */ /** + * Allocate descriptor ring + * + * @v intelxl Intel device + * @v ring Descriptor ring + * @ret rc Return status code + */ +int intelxl_alloc_ring ( struct intelxl_nic *intelxl, + struct intelxl_ring *ring ) { + physaddr_t address; + int rc; + + /* Allocate descriptor ring */ + ring->desc.raw = malloc_dma ( ring->len, INTELXL_ALIGN ); + if ( ! ring->desc.raw ) { + rc = -ENOMEM; + goto err_alloc; + } + address = virt_to_bus ( ring->desc.raw ); + + /* Initialise descriptor ring */ + memset ( ring->desc.raw, 0, ring->len ); + + /* Reset tail pointer */ + writel ( 0, ( intelxl->regs + ring->tail ) ); + + /* Reset counters */ + ring->prod = 0; + ring->cons = 0; + + DBGC ( intelxl, "INTELXL %p ring %06x is at [%08llx,%08llx)\n", + intelxl, ( ring->reg + ring->tail ), + ( ( unsigned long long ) address ), + ( ( unsigned long long ) address + ring->len ) ); + + return 0; + + free_dma ( ring->desc.raw, ring->len ); + err_alloc: + return rc; +} + +/** + * Free descriptor ring + * + * @v intelxl Intel device + * @v ring Descriptor ring + */ +void intelxl_free_ring ( struct intelxl_nic *intelxl __unused, + struct intelxl_ring *ring ) { + + /* Free descriptor ring */ + free_dma ( ring->desc.raw, ring->len ); + ring->desc.raw = NULL; +} + +/** * Dump queue context (for debugging) * * @v intelxl Intel device @@ -861,7 +1155,7 @@ static int intelxl_context_rx ( struct intelxl_nic *intelxl, base_count = INTELXL_CTX_RX_BASE_COUNT ( address, INTELXL_RX_NUM_DESC ); ctx.rx.base_count = cpu_to_le64 ( base_count ); ctx.rx.len = cpu_to_le16 ( INTELXL_CTX_RX_LEN ( intelxl->mfs ) ); - ctx.rx.flags = INTELXL_CTX_RX_FL_CRCSTRIP; + ctx.rx.flags = ( INTELXL_CTX_RX_FL_DSIZE | INTELXL_CTX_RX_FL_CRCSTRIP ); ctx.rx.mfs = cpu_to_le16 ( INTELXL_CTX_RX_MFS ( intelxl->mfs ) ); /* Program context */ @@ -939,25 +1233,15 @@ static int intelxl_disable_ring ( struct intelxl_nic *intelxl, */ static int intelxl_create_ring ( struct intelxl_nic *intelxl, struct intelxl_ring *ring ) { - void *ring_regs = ( intelxl->regs + ring->reg ); physaddr_t address; int rc; /* Allocate descriptor ring */ - ring->desc = malloc_dma ( ring->len, INTELXL_ALIGN ); - if ( ! ring->desc ) { - rc = -ENOMEM; + if ( ( rc = intelxl_alloc_ring ( intelxl, ring ) ) != 0 ) goto err_alloc; - } - - /* Initialise descriptor ring */ - memset ( ring->desc, 0, ring->len ); - - /* Reset tail pointer */ - writel ( 0, ( ring_regs + INTELXL_QXX_TAIL ) ); /* Program queue context */ - address = virt_to_bus ( ring->desc ); + address = virt_to_bus ( ring->desc.raw ); if ( ( rc = ring->context ( intelxl, address ) ) != 0 ) goto err_context; @@ -965,20 +1249,12 @@ static int intelxl_create_ring ( struct intelxl_nic *intelxl, if ( ( rc = intelxl_enable_ring ( intelxl, ring ) ) != 0 ) goto err_enable; - /* Reset counters */ - ring->prod = 0; - ring->cons = 0; - - DBGC ( intelxl, "INTELXL %p ring %06x is at [%08llx,%08llx)\n", - intelxl, ring->reg, ( ( unsigned long long ) address ), - ( ( unsigned long long ) address + ring->len ) ); - return 0; intelxl_disable_ring ( intelxl, ring ); err_enable: err_context: - free_dma ( ring->desc, ring->len ); + intelxl_free_ring ( intelxl, ring ); err_alloc: return rc; } @@ -1000,8 +1276,7 @@ static void intelxl_destroy_ring ( struct intelxl_nic *intelxl, } /* Free descriptor ring */ - free_dma ( ring->desc, ring->len ); - ring->desc = NULL; + intelxl_free_ring ( intelxl, ring ); } /** @@ -1029,7 +1304,7 @@ static void intelxl_refill_rx ( struct intelxl_nic *intelxl ) { /* Get next receive descriptor */ rx_idx = ( intelxl->rx.prod++ % INTELXL_RX_NUM_DESC ); - rx = &intelxl->rx.desc[rx_idx].rx; + rx = &intelxl->rx.desc.rx[rx_idx].data; /* Populate receive descriptor */ address = virt_to_bus ( iobuf->data ); @@ -1050,8 +1325,23 @@ static void intelxl_refill_rx ( struct intelxl_nic *intelxl ) { if ( refilled ) { wmb(); rx_tail = ( intelxl->rx.prod % INTELXL_RX_NUM_DESC ); - writel ( rx_tail, - ( intelxl->regs + intelxl->rx.reg + INTELXL_QXX_TAIL)); + writel ( rx_tail, ( intelxl->regs + intelxl->rx.tail ) ); + } +} + +/** + * Discard unused receive I/O buffers + * + * @v intelxl Intel device + */ +void intelxl_empty_rx ( struct intelxl_nic *intelxl ) { + unsigned int i; + + /* Discard any unused receive buffers */ + for ( i = 0 ; i < INTELXL_RX_NUM_DESC ; i++ ) { + if ( intelxl->rx_iobuf[i] ) + free_iob ( intelxl->rx_iobuf[i] ); + intelxl->rx_iobuf[i] = NULL; } } @@ -1141,7 +1431,6 @@ static int intelxl_open ( struct net_device *netdev ) { static void intelxl_close ( struct net_device *netdev ) { struct intelxl_nic *intelxl = netdev->priv; unsigned int queue; - unsigned int i; /* Dump contexts (for debugging) */ intelxl_context_dump ( intelxl, INTELXL_PFCM_LANCTXCTL_TYPE_TX, @@ -1163,11 +1452,7 @@ static void intelxl_close ( struct net_device *netdev ) { intelxl_destroy_ring ( intelxl, &intelxl->rx ); /* Discard any unused receive buffers */ - for ( i = 0 ; i < INTELXL_RX_NUM_DESC ; i++ ) { - if ( intelxl->rx_iobuf[i] ) - free_iob ( intelxl->rx_iobuf[i] ); - intelxl->rx_iobuf[i] = NULL; - } + intelxl_empty_rx ( intelxl ); } /** @@ -1177,8 +1462,7 @@ static void intelxl_close ( struct net_device *netdev ) { * @v iobuf I/O buffer * @ret rc Return status code */ -static int intelxl_transmit ( struct net_device *netdev, - struct io_buffer *iobuf ) { +int intelxl_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { struct intelxl_nic *intelxl = netdev->priv; struct intelxl_tx_data_descriptor *tx; unsigned int tx_idx; @@ -1194,7 +1478,7 @@ static int intelxl_transmit ( struct net_device *netdev, } tx_idx = ( intelxl->tx.prod++ % INTELXL_TX_NUM_DESC ); tx_tail = ( intelxl->tx.prod % INTELXL_TX_NUM_DESC ); - tx = &intelxl->tx.desc[tx_idx].tx; + tx = &intelxl->tx.desc.tx[tx_idx].data; /* Populate transmit descriptor */ address = virt_to_bus ( iobuf->data ); @@ -1206,8 +1490,7 @@ static int intelxl_transmit ( struct net_device *netdev, wmb(); /* Notify card that there are packets ready to transmit */ - writel ( tx_tail, - ( intelxl->regs + intelxl->tx.reg + INTELXL_QXX_TAIL ) ); + writel ( tx_tail, ( intelxl->regs + intelxl->tx.tail ) ); DBGC2 ( intelxl, "INTELXL %p TX %d is [%llx,%llx)\n", intelxl, tx_idx, ( ( unsigned long long ) address ), @@ -1230,7 +1513,7 @@ static void intelxl_poll_tx ( struct net_device *netdev ) { /* Get next transmit descriptor */ tx_idx = ( intelxl->tx.cons % INTELXL_TX_NUM_DESC ); - tx_wb = &intelxl->tx.desc[tx_idx].tx_wb; + tx_wb = &intelxl->tx.desc.tx[tx_idx].wb; /* Stop if descriptor is still in use */ if ( ! ( tx_wb->flags & INTELXL_TX_WB_FL_DD ) ) @@ -1254,6 +1537,7 @@ static void intelxl_poll_rx ( struct net_device *netdev ) { struct intelxl_rx_writeback_descriptor *rx_wb; struct io_buffer *iobuf; unsigned int rx_idx; + unsigned int tag; size_t len; /* Check for received packets */ @@ -1261,7 +1545,7 @@ static void intelxl_poll_rx ( struct net_device *netdev ) { /* Get next receive descriptor */ rx_idx = ( intelxl->rx.cons % INTELXL_RX_NUM_DESC ); - rx_wb = &intelxl->rx.desc[rx_idx].rx_wb; + rx_wb = &intelxl->rx.desc.rx[rx_idx].wb; /* Stop if descriptor is still in use */ if ( ! ( rx_wb->flags & cpu_to_le32 ( INTELXL_RX_WB_FL_DD ) ) ) @@ -1273,16 +1557,23 @@ static void intelxl_poll_rx ( struct net_device *netdev ) { len = INTELXL_RX_WB_LEN ( le32_to_cpu ( rx_wb->len ) ); iob_put ( iobuf, len ); + /* Find VLAN device, if applicable */ + if ( rx_wb->flags & cpu_to_le32 ( INTELXL_RX_WB_FL_VLAN ) ) { + tag = VLAN_TAG ( le16_to_cpu ( rx_wb->vlan ) ); + } else { + tag = 0; + } + /* Hand off to network stack */ if ( rx_wb->flags & cpu_to_le32 ( INTELXL_RX_WB_FL_RXE ) ) { DBGC ( intelxl, "INTELXL %p RX %d error (length %zd, " "flags %08x)\n", intelxl, rx_idx, len, le32_to_cpu ( rx_wb->flags ) ); - netdev_rx_err ( netdev, iobuf, -EIO ); + vlan_netdev_rx_err ( netdev, tag, iobuf, -EIO ); } else { DBGC2 ( intelxl, "INTELXL %p RX %d complete (length " "%zd)\n", intelxl, rx_idx, len ); - netdev_rx ( netdev, iobuf ); + vlan_netdev_rx ( netdev, tag, iobuf ); } intelxl->rx.cons++; } @@ -1293,16 +1584,9 @@ static void intelxl_poll_rx ( struct net_device *netdev ) { * * @v netdev Network device */ -static void intelxl_poll ( struct net_device *netdev ) { +void intelxl_poll ( struct net_device *netdev ) { struct intelxl_nic *intelxl = netdev->priv; - /* Acknowledge interrupts, if applicable */ - if ( netdev_irq_enabled ( netdev ) ) { - writel ( ( INTELXL_PFINT_DYN_CTL0_CLEARPBA | - INTELXL_PFINT_DYN_CTL0_INTENA_MASK ), - intelxl->regs + INTELXL_PFINT_DYN_CTL0 ); - } - /* Poll for completed packets */ intelxl_poll_tx ( netdev ); @@ -1314,23 +1598,23 @@ static void intelxl_poll ( struct net_device *netdev ) { /* Refill RX ring */ intelxl_refill_rx ( intelxl ); -} - -/** - * Enable or disable interrupts - * - * @v netdev Network device - * @v enable Interrupts should be enabled - */ -static void intelxl_irq ( struct net_device *netdev, int enable ) { - struct intelxl_nic *intelxl = netdev->priv; - if ( enable ) { - writel ( INTELXL_PFINT_DYN_CTL0_INTENA, - intelxl->regs + INTELXL_PFINT_DYN_CTL0 ); - } else { - writel ( 0, intelxl->regs + INTELXL_PFINT_DYN_CTL0 ); - } + /* Rearm interrupt, since otherwise receive descriptors will + * be written back only after a complete cacheline (four + * packets) have been received. + * + * There is unfortunately no efficient way to determine + * whether or not rearming the interrupt is necessary. If we + * are running inside a hypervisor (e.g. using a VF or PF as a + * passed-through PCI device), then the MSI-X write is + * redirected by the hypervisor to the real host APIC and the + * host ISR then raises an interrupt within the guest. We + * therefore cannot poll the nominal MSI-X target location to + * watch for the value being written. We could read from the + * INT_DYN_CTL register, but this is even less efficient than + * just unconditionally rearming the interrupt. + */ + writel ( INTELXL_INT_DYN_CTL_INTENA, intelxl->regs + intelxl->intr ); } /** Network device operations */ @@ -1339,7 +1623,6 @@ static struct net_device_operations intelxl_operations = { .close = intelxl_close, .transmit = intelxl_transmit, .poll = intelxl_poll, - .irq = intelxl_irq, }; /****************************************************************************** @@ -1374,11 +1657,16 @@ static int intelxl_probe ( struct pci_device *pci ) { netdev->dev = &pci->dev; memset ( intelxl, 0, sizeof ( *intelxl ) ); intelxl->pf = PCI_FUNC ( pci->busdevfn ); - intelxl_init_admin ( &intelxl->command, INTELXL_ADMIN_CMD ); - intelxl_init_admin ( &intelxl->event, INTELXL_ADMIN_EVT ); + intelxl->intr = INTELXL_PFINT_DYN_CTL0; + intelxl_init_admin ( &intelxl->command, INTELXL_ADMIN_CMD, + &intelxl_admin_offsets ); + intelxl_init_admin ( &intelxl->event, INTELXL_ADMIN_EVT, + &intelxl_admin_offsets ); intelxl_init_ring ( &intelxl->tx, INTELXL_TX_NUM_DESC, + sizeof ( intelxl->tx.desc.tx[0] ), intelxl_context_tx ); intelxl_init_ring ( &intelxl->rx, INTELXL_RX_NUM_DESC, + sizeof ( intelxl->rx.desc.rx[0] ), intelxl_context_rx ); /* Fix up PCI device */ @@ -1408,10 +1696,18 @@ static int intelxl_probe ( struct pci_device *pci ) { if ( ( rc = intelxl_fetch_mac ( intelxl, netdev ) ) != 0 ) goto err_fetch_mac; + /* Enable MSI-X dummy interrupt */ + if ( ( rc = intelxl_msix_enable ( intelxl, pci ) ) != 0 ) + goto err_msix; + /* Open admin queues */ if ( ( rc = intelxl_open_admin ( intelxl ) ) != 0 ) goto err_open_admin; + /* Clear PXE mode */ + if ( ( rc = intelxl_admin_clear_pxe ( intelxl ) ) != 0 ) + goto err_admin_clear_pxe; + /* Get switch configuration */ if ( ( rc = intelxl_admin_switch ( intelxl ) ) != 0 ) goto err_admin_switch; @@ -1426,7 +1722,9 @@ static int intelxl_probe ( struct pci_device *pci ) { /* Configure queue register addresses */ intelxl->tx.reg = INTELXL_QTX ( intelxl->queue ); + intelxl->tx.tail = ( intelxl->tx.reg + INTELXL_QXX_TAIL ); intelxl->rx.reg = INTELXL_QRX ( intelxl->queue ); + intelxl->rx.tail = ( intelxl->rx.reg + INTELXL_QXX_TAIL ); /* Configure interrupt causes */ writel ( ( INTELXL_QINT_TQCTL_NEXTQ_INDX_NONE | @@ -1456,8 +1754,11 @@ static int intelxl_probe ( struct pci_device *pci ) { err_admin_promisc: err_admin_vsi: err_admin_switch: + err_admin_clear_pxe: intelxl_close_admin ( intelxl ); err_open_admin: + intelxl_msix_disable ( intelxl, pci ); + err_msix: err_fetch_mac: intelxl_reset ( intelxl ); err_reset: @@ -1484,6 +1785,9 @@ static void intelxl_remove ( struct pci_device *pci ) { /* Close admin queues */ intelxl_close_admin ( intelxl ); + /* Disable MSI-X dummy interrupt */ + intelxl_msix_disable ( intelxl, pci ); + /* Reset the NIC */ intelxl_reset ( intelxl ); diff --git a/src/drivers/net/intelxl.h b/src/drivers/net/intelxl.h index 02d9b98a2..80586cef0 100644 --- a/src/drivers/net/intelxl.h +++ b/src/drivers/net/intelxl.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <ipxe/if_ether.h> +#include <ipxe/pcimsix.h> struct intelxl_nic; @@ -19,9 +20,9 @@ struct intelxl_nic; /** Alignment * - * No data structure requires greater than 128 byte alignment. + * No data structure requires greater than 256 byte alignment. */ -#define INTELXL_ALIGN 128 +#define INTELXL_ALIGN 256 /****************************************************************************** * @@ -53,6 +54,24 @@ struct intelxl_nic; /** Admin Queue Tail Register (offset) */ #define INTELXL_ADMIN_TAIL 0x400 +/** Admin queue register offsets + * + * The physical and virtual function register maps have no discernible + * relationship. + */ +struct intelxl_admin_offsets { + /** Base Address Low Register offset */ + unsigned int bal; + /** Base Address High Register offset */ + unsigned int bah; + /** Length Register offset */ + unsigned int len; + /** Head Register offset */ + unsigned int head; + /** Tail Register offset */ + unsigned int tail; +}; + /** Admin queue data buffer command parameters */ struct intelxl_admin_buffer_params { /** Reserved */ @@ -125,6 +144,20 @@ struct intelxl_admin_shutdown_params { /** Driver is unloading */ #define INTELXL_ADMIN_SHUTDOWN_UNLOADING 0x01 +/** Admin queue Clear PXE Mode command */ +#define INTELXL_ADMIN_CLEAR_PXE 0x0110 + +/** Admin queue Clear PXE Mode command parameters */ +struct intelxl_admin_clear_pxe_params { + /** Magic value */ + uint8_t magic; + /** Reserved */ + uint8_t reserved[15]; +} __attribute__ (( packed )); + +/** Clear PXE Mode magic value */ +#define INTELXL_ADMIN_CLEAR_PXE_MAGIC 0x02 + /** Admin queue Get Switch Configuration command */ #define INTELXL_ADMIN_SWITCH 0x0200 @@ -271,6 +304,172 @@ struct intelxl_admin_link_params { /** Link is up */ #define INTELXL_ADMIN_LINK_UP 0x01 +/** Admin queue Send Message to PF command */ +#define INTELXL_ADMIN_SEND_TO_PF 0x0801 + +/** Admin queue Send Message to VF command */ +#define INTELXL_ADMIN_SEND_TO_VF 0x0802 + +/** Admin Queue VF Reset opcode */ +#define INTELXL_ADMIN_VF_RESET 0x00000002 + +/** Admin Queue VF Get Resources opcode */ +#define INTELXL_ADMIN_VF_GET_RESOURCES 0x00000003 + +/** Admin Queue VF Get Resources data buffer */ +struct intelxl_admin_vf_get_resources_buffer { + /** Reserved */ + uint8_t reserved_a[20]; + /** VSI switching element ID */ + uint16_t vsi; + /** Reserved */ + uint8_t reserved_b[8]; + /** MAC address */ + uint8_t mac[ETH_ALEN]; +} __attribute__ (( packed )); + +/** Admin Queue VF Status Change Event opcode */ +#define INTELXL_ADMIN_VF_STATUS 0x00000011 + +/** Link status change event type */ +#define INTELXL_ADMIN_VF_STATUS_LINK 0x00000001 + +/** Link status change event data */ +struct intelxl_admin_vf_status_link { + /** Link speed */ + uint32_t speed; + /** Link status */ + uint8_t status; + /** Reserved */ + uint8_t reserved[3]; +} __attribute__ (( packed )); + +/** Admin Queue VF Status Change Event data buffer */ +struct intelxl_admin_vf_status_buffer { + /** Event type */ + uint32_t event; + /** Event data */ + union { + /** Link change event data */ + struct intelxl_admin_vf_status_link link; + } data; + /** Reserved */ + uint8_t reserved[4]; +} __attribute__ (( packed )); + +/** Admin Queue VF Configure Queues opcode */ +#define INTELXL_ADMIN_VF_CONFIGURE 0x00000006 + +/** Admin Queue VF Configure Queues data buffer */ +struct intelxl_admin_vf_configure_buffer { + /** VSI switching element ID */ + uint16_t vsi; + /** Number of queue pairs */ + uint16_t count; + /** Reserved */ + uint8_t reserved_a[4]; + /** Transmit queue */ + struct { + /** VSI switching element ID */ + uint16_t vsi; + /** Queue ID */ + uint16_t id; + /** Queue count */ + uint16_t count; + /** Reserved */ + uint8_t reserved_a[2]; + /** Base address */ + uint64_t base; + /** Reserved */ + uint8_t reserved_b[8]; + } __attribute__ (( packed )) tx; + /** Receive queue */ + struct { + /** VSI switching element ID */ + uint16_t vsi; + /** Queue ID */ + uint16_t id; + /** Queue count */ + uint32_t count; + /** Reserved */ + uint8_t reserved_a[4]; + /** Data buffer length */ + uint32_t len; + /** Maximum frame size */ + uint32_t mfs; + /** Reserved */ + uint8_t reserved_b[4]; + /** Base address */ + uint64_t base; + /** Reserved */ + uint8_t reserved_c[8]; + } __attribute__ (( packed )) rx; + /** Reserved + * + * This field exists only due to a bug in the PF driver's + * message validation logic, which causes it to miscalculate + * the expected message length. + */ + uint8_t reserved_b[64]; +} __attribute__ (( packed )); + +/** Admin Queue VF IRQ Map opcode */ +#define INTELXL_ADMIN_VF_IRQ_MAP 0x00000007 + +/** Admin Queue VF IRQ Map data buffer */ +struct intelxl_admin_vf_irq_map_buffer { + /** Number of interrupt vectors */ + uint16_t count; + /** VSI switching element ID */ + uint16_t vsi; + /** Interrupt vector ID */ + uint16_t vec; + /** Receive queue bitmap */ + uint16_t rxmap; + /** Transmit queue bitmap */ + uint16_t txmap; + /** Receive interrupt throttling index */ + uint16_t rxitr; + /** Transmit interrupt throttling index */ + uint16_t txitr; + /** Reserved + * + * This field exists only due to a bug in the PF driver's + * message validation logic, which causes it to miscalculate + * the expected message length. + */ + uint8_t reserved[12]; +} __attribute__ (( packed )); + +/** Admin Queue VF Enable Queues opcode */ +#define INTELXL_ADMIN_VF_ENABLE 0x00000008 + +/** Admin Queue VF Disable Queues opcode */ +#define INTELXL_ADMIN_VF_DISABLE 0x00000009 + +/** Admin Queue VF Enable/Disable Queues data buffer */ +struct intelxl_admin_vf_queues_buffer { + /** VSI switching element ID */ + uint16_t vsi; + /** Reserved */ + uint8_t reserved[2]; + /** Receive queue bitmask */ + uint32_t rx; + /** Transmit queue bitmask */ + uint32_t tx; +} __attribute__ (( packed )); + +/** Admin Queue VF Configure Promiscuous Mode opcode */ +#define INTELXL_ADMIN_VF_PROMISC 0x0000000e + +/** Admin Queue VF Configure Promiscuous Mode data buffer */ +struct intelxl_admin_vf_promisc_buffer { + /** VSI switching element ID */ + uint16_t vsi; + /** Flags */ + uint16_t flags; +} __attribute__ (( packed )); + /** Admin queue command parameters */ union intelxl_admin_params { /** Additional data buffer command parameters */ @@ -281,6 +480,8 @@ union intelxl_admin_params { struct intelxl_admin_driver_params driver; /** Shutdown command parameters */ struct intelxl_admin_shutdown_params shutdown; + /** Clear PXE Mode command parameters */ + struct intelxl_admin_clear_pxe_params pxe; /** Get Switch Configuration command parameters */ struct intelxl_admin_switch_params sw; /** Get VSI Parameters command parameters */ @@ -301,6 +502,20 @@ union intelxl_admin_buffer { struct intelxl_admin_switch_buffer sw; /** Get VSI Parameters data buffer */ struct intelxl_admin_vsi_buffer vsi; + /** VF Get Resources data buffer */ + struct intelxl_admin_vf_get_resources_buffer res; + /** VF Status Change Event data buffer */ + struct intelxl_admin_vf_status_buffer stat; + /** VF Configure Queues data buffer */ + struct intelxl_admin_vf_configure_buffer cfg; + /** VF Enable/Disable Queues data buffer */ + struct intelxl_admin_vf_queues_buffer queues; + /** VF Configure Promiscuous Mode data buffer */ + struct intelxl_admin_vf_promisc_buffer promisc; + /*** VF IRQ Map data buffer */ + struct intelxl_admin_vf_irq_map_buffer irq; + /** Alignment padding */ + uint8_t pad[INTELXL_ALIGN]; } __attribute__ (( packed )); /** Admin queue descriptor */ @@ -313,10 +528,15 @@ struct intelxl_admin_descriptor { uint16_t len; /** Return value */ uint16_t ret; - /** Cookie */ - uint32_t cookie; - /** Reserved */ - uint32_t reserved; + /** Opaque cookie / VF opcode */ + union { + /** Cookie */ + uint32_t cookie; + /** VF opcode */ + uint32_t vopcode; + }; + /** VF return value */ + int32_t vret; /** Parameters */ union intelxl_admin_params params; } __attribute__ (( packed )); @@ -340,25 +560,30 @@ struct intelxl_admin_descriptor { struct intelxl_admin { /** Descriptors */ struct intelxl_admin_descriptor *desc; + /** Data buffers */ + union intelxl_admin_buffer *buf; /** Queue index */ unsigned int index; - /** Register block */ - unsigned int reg; - /** Data buffer */ - union intelxl_admin_buffer *buffer; + /** Register block base */ + unsigned int base; + /** Register offsets */ + const struct intelxl_admin_offsets *regs; }; /** * Initialise admin queue * * @v admin Admin queue - * @v reg Register block + * @v base Register block base + * @v regs Register offsets */ static inline __attribute__ (( always_inline )) void -intelxl_init_admin ( struct intelxl_admin *admin, unsigned int reg ) { +intelxl_init_admin ( struct intelxl_admin *admin, unsigned int base, + const struct intelxl_admin_offsets *regs ) { - admin->reg = reg; + admin->base = base; + admin->regs = regs; } /** Number of admin queue descriptors */ @@ -466,6 +691,9 @@ struct intelxl_context_rx { /** Receive queue data buffer length */ #define INTELXL_CTX_RX_LEN( len ) ( (len) >> 1 ) +/** Use 32-byte receive descriptors */ +#define INTELXL_CTX_RX_FL_DSIZE 0x10 + /** Strip CRC from received packets */ #define INTELXL_CTX_RX_FL_CRCSTRIP 0x20 @@ -524,6 +752,10 @@ struct intelxl_context_rx { /** Queue Tail Pointer Register (offset) */ #define INTELXL_QXX_TAIL 0x8000 +/** Global RLAN Control 0 register */ +#define INTELXL_GLLAN_RCTL_0 0x12a500 +#define INTELXL_GLLAN_RCTL_0_PXE_MODE 0x00000001UL /**< PXE mode */ + /** Transmit data descriptor */ struct intelxl_tx_data_descriptor { /** Buffer address */ @@ -569,6 +801,14 @@ struct intelxl_tx_writeback_descriptor { /** Transmit writeback descriptor complete */ #define INTELXL_TX_WB_FL_DD 0x01 +/** Transmit descriptor */ +union intelxl_tx_descriptor { + /** Transmit data descriptor */ + struct intelxl_tx_data_descriptor data; + /** Transmit writeback descriptor */ + struct intelxl_tx_writeback_descriptor wb; +}; + /** Receive data descriptor */ struct intelxl_rx_data_descriptor { /** Buffer address */ @@ -576,22 +816,31 @@ struct intelxl_rx_data_descriptor { /** Flags */ uint32_t flags; /** Reserved */ - uint8_t reserved[4]; + uint8_t reserved[20]; } __attribute__ (( packed )); /** Receive writeback descriptor */ struct intelxl_rx_writeback_descriptor { /** Reserved */ - uint8_t reserved[8]; + uint8_t reserved_a[2]; + /** VLAN tag */ + uint16_t vlan; + /** Reserved */ + uint8_t reserved_b[4]; /** Flags */ uint32_t flags; /** Length */ uint32_t len; + /** Reserved */ + uint8_t reserved_c[16]; } __attribute__ (( packed )); /** Receive writeback descriptor complete */ #define INTELXL_RX_WB_FL_DD 0x00000001UL +/** Receive writeback descriptor VLAN tag present */ +#define INTELXL_RX_WB_FL_VLAN 0x00000004UL + /** Receive writeback descriptor error */ #define INTELXL_RX_WB_FL_RXE 0x00080000UL @@ -599,21 +848,24 @@ struct intelxl_rx_writeback_descriptor { #define INTELXL_RX_WB_LEN(len) ( ( (len) >> 6 ) & 0x3fff ) /** Packet descriptor */ -union intelxl_descriptor { - /** Transmit data descriptor */ - struct intelxl_tx_data_descriptor tx; - /** Transmit writeback descriptor */ - struct intelxl_tx_writeback_descriptor tx_wb; +union intelxl_rx_descriptor { /** Receive data descriptor */ - struct intelxl_rx_data_descriptor rx; + struct intelxl_rx_data_descriptor data; /** Receive writeback descriptor */ - struct intelxl_rx_writeback_descriptor rx_wb; + struct intelxl_rx_writeback_descriptor wb; }; /** Descriptor ring */ struct intelxl_ring { /** Descriptors */ - union intelxl_descriptor *desc; + union { + /** Transmit descriptors */ + union intelxl_tx_descriptor *tx; + /** Receive descriptors */ + union intelxl_rx_descriptor *rx; + /** Raw data */ + void *raw; + } desc; /** Producer index */ unsigned int prod; /** Consumer index */ @@ -621,6 +873,8 @@ struct intelxl_ring { /** Register block */ unsigned int reg; + /** Tail register */ + unsigned int tail; /** Length (in bytes) */ size_t len; /** Program queue context @@ -636,33 +890,39 @@ struct intelxl_ring { * * @v ring Descriptor ring * @v count Number of descriptors + * @v len Length of a single descriptor * @v context Method to program queue context */ static inline __attribute__ (( always_inline)) void -intelxl_init_ring ( struct intelxl_ring *ring, unsigned int count, +intelxl_init_ring ( struct intelxl_ring *ring, unsigned int count, size_t len, int ( * context ) ( struct intelxl_nic *intelxl, physaddr_t address ) ) { - ring->len = ( count * sizeof ( ring->desc[0] ) ); + ring->len = ( count * len ); ring->context = context; } -/** Number of transmit descriptors */ -#define INTELXL_TX_NUM_DESC 16 +/** Number of transmit descriptors + * + * Chosen to exceed the receive ring fill level, in order to avoid + * running out of transmit descriptors when sending TCP ACKs. + */ +#define INTELXL_TX_NUM_DESC 64 /** Transmit descriptor ring maximum fill level */ #define INTELXL_TX_FILL ( INTELXL_TX_NUM_DESC - 1 ) /** Number of receive descriptors * - * In PXE mode (i.e. able to post single receive descriptors), 8 - * descriptors is the only permitted value covering all possible - * numbers of PFs. + * Must be a multiple of 32. */ -#define INTELXL_RX_NUM_DESC 8 +#define INTELXL_RX_NUM_DESC 32 -/** Receive descriptor ring fill level */ -#define INTELXL_RX_FILL ( INTELXL_RX_NUM_DESC - 1 ) +/** Receive descriptor ring fill level + * + * Must be a multiple of 8 and greater than 8. + */ +#define INTELXL_RX_FILL 16 /****************************************************************************** * @@ -673,9 +933,9 @@ intelxl_init_ring ( struct intelxl_ring *ring, unsigned int count, /** PF Interrupt Zero Dynamic Control Register */ #define INTELXL_PFINT_DYN_CTL0 0x038480 -#define INTELXL_PFINT_DYN_CTL0_INTENA 0x00000001UL /**< Enable */ -#define INTELXL_PFINT_DYN_CTL0_CLEARPBA 0x00000002UL /**< Acknowledge */ -#define INTELXL_PFINT_DYN_CTL0_INTENA_MASK 0x80000000UL /**< Ignore enable */ +#define INTELXL_INT_DYN_CTL_INTENA 0x00000001UL /**< Enable */ +#define INTELXL_INT_DYN_CTL_CLEARPBA 0x00000002UL /**< Acknowledge */ +#define INTELXL_INT_DYN_CTL_INTENA_MASK 0x80000000UL /**< Ignore enable */ /** PF Interrupt Zero Linked List Register */ #define INTELXL_PFINT_LNKLST0 0x038500 @@ -773,12 +1033,27 @@ struct intelxl_nic { unsigned int vsi; /** Queue set handle */ unsigned int qset; + /** Interrupt control register */ + unsigned int intr; + /** MSI-X capability */ + struct pci_msix msix; + /** MSI-X dummy interrupt target */ + uint32_t msg; + /** PCI Express capability offset */ + unsigned int exp; /** Admin command queue */ struct intelxl_admin command; /** Admin event queue */ struct intelxl_admin event; + /** Current VF opcode */ + unsigned int vopcode; + /** Current VF return value */ + int vret; + /** Current VF event data buffer */ + union intelxl_admin_buffer vbuf; + /** Transmit descriptor ring */ struct intelxl_ring tx; /** Receive descriptor ring */ @@ -787,4 +1062,30 @@ struct intelxl_nic { struct io_buffer *rx_iobuf[INTELXL_RX_NUM_DESC]; }; +extern int intelxl_msix_enable ( struct intelxl_nic *intelxl, + struct pci_device *pci ); +extern void intelxl_msix_disable ( struct intelxl_nic *intelxl, + struct pci_device *pci ); +extern struct intelxl_admin_descriptor * +intelxl_admin_command_descriptor ( struct intelxl_nic *intelxl ); +extern union intelxl_admin_buffer * +intelxl_admin_command_buffer ( struct intelxl_nic *intelxl ); +extern int intelxl_admin_command ( struct intelxl_nic *intelxl ); +extern void intelxl_poll_admin ( struct net_device *netdev ); +extern int intelxl_open_admin ( struct intelxl_nic *intelxl ); +extern void intelxl_reopen_admin ( struct intelxl_nic *intelxl ); +extern void intelxl_close_admin ( struct intelxl_nic *intelxl ); +extern int intelxl_alloc_ring ( struct intelxl_nic *intelxl, + struct intelxl_ring *ring ); +extern void intelxl_free_ring ( struct intelxl_nic *intelxl, + struct intelxl_ring *ring ); +extern void intelxl_empty_rx ( struct intelxl_nic *intelxl ); +extern int intelxl_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ); +extern void intelxl_poll ( struct net_device *netdev ); + +extern void intelxlvf_admin_event ( struct net_device *netdev, + struct intelxl_admin_descriptor *evt, + union intelxl_admin_buffer *buf ); + #endif /* _INTELXL_H */ diff --git a/src/drivers/net/intelxlvf.c b/src/drivers/net/intelxlvf.c new file mode 100644 index 000000000..8f76daf3d --- /dev/null +++ b/src/drivers/net/intelxlvf.c @@ -0,0 +1,719 @@ +/* + * Copyright (C) 2019 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 (at your option) 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 ); + +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/pci.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include "intelxlvf.h" + +/** @file + * + * Intel 40 Gigabit Ethernet virtual function network card driver + * + */ + +/****************************************************************************** + * + * Device reset + * + ****************************************************************************** + */ + +/** + * Reset hardware via PCIe function-level reset + * + * @v intelxl Intel device + */ +static void intelxlvf_reset_flr ( struct intelxl_nic *intelxl, + struct pci_device *pci ) { + uint16_t control; + + /* Perform a PCIe function-level reset */ + pci_read_config_word ( pci, ( intelxl->exp + PCI_EXP_DEVCTL ), + &control ); + pci_write_config_word ( pci, ( intelxl->exp + PCI_EXP_DEVCTL ), + ( control | PCI_EXP_DEVCTL_FLR ) ); + mdelay ( INTELXL_RESET_DELAY_MS ); +} + +/** + * Wait for admin event queue to be torn down + * + * @v intelxl Intel device + * @ret rc Return status code + */ +static int intelxlvf_reset_wait_teardown ( struct intelxl_nic *intelxl ) { + uint32_t admin_evt_len; + unsigned int i; + + /* Wait for admin event queue to be torn down */ + for ( i = 0 ; i < INTELXLVF_RESET_MAX_WAIT_MS ; i++ ) { + + /* Check admin event queue length register */ + admin_evt_len = readl ( intelxl->regs + INTELXLVF_ADMIN + + INTELXLVF_ADMIN_EVT_LEN ); + if ( ! ( admin_evt_len & INTELXL_ADMIN_LEN_ENABLE ) ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( intelxl, "INTELXL %p timed out waiting for teardown (%#08x)\n", + intelxl, admin_evt_len ); + return -ETIMEDOUT; +} + +/** + * Wait for virtual function to be marked as active + * + * @v intelxl Intel device + * @ret rc Return status code + */ +static int intelxlvf_reset_wait_active ( struct intelxl_nic *intelxl ) { + uint32_t vfgen_rstat; + unsigned int vfr_state; + unsigned int i; + + /* Wait for virtual function to be marked as active */ + for ( i = 0 ; i < INTELXLVF_RESET_MAX_WAIT_MS ; i++ ) { + + /* Check status as written by physical function driver */ + vfgen_rstat = readl ( intelxl->regs + INTELXLVF_VFGEN_RSTAT ); + vfr_state = INTELXLVF_VFGEN_RSTAT_VFR_STATE ( vfgen_rstat ); + if ( vfr_state == INTELXLVF_VFGEN_RSTAT_VFR_STATE_ACTIVE ) + return 0; + + /* Delay */ + mdelay ( 1 ); + } + + DBGC ( intelxl, "INTELXL %p timed out waiting for activation " + "(%#08x)\n", intelxl, vfgen_rstat ); + return -ETIMEDOUT; +} + +/** + * Reset hardware via admin queue + * + * @v intelxl Intel device + * @ret rc Return status code + */ +static int intelxlvf_reset_admin ( struct intelxl_nic *intelxl ) { + struct intelxl_admin_descriptor *cmd; + int rc; + + /* Populate descriptor */ + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_SEND_TO_PF ); + cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_RESET ); + + /* Issue command */ + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) + goto err_command; + + /* Wait for minimum reset time */ + mdelay ( INTELXL_RESET_DELAY_MS ); + + /* Wait for reset to take effect */ + if ( ( rc = intelxlvf_reset_wait_teardown ( intelxl ) ) != 0 ) + goto err_teardown; + + /* Wait for virtual function to become active */ + if ( ( rc = intelxlvf_reset_wait_active ( intelxl ) ) != 0 ) + goto err_active; + + err_active: + err_teardown: + intelxl_reopen_admin ( intelxl ); + err_command: + return rc; +} + +/****************************************************************************** + * + * Admin queue + * + ****************************************************************************** + */ + +/** Admin command queue register offsets */ +static const struct intelxl_admin_offsets intelxlvf_admin_command_offsets = { + .bal = INTELXLVF_ADMIN_CMD_BAL, + .bah = INTELXLVF_ADMIN_CMD_BAH, + .len = INTELXLVF_ADMIN_CMD_LEN, + .head = INTELXLVF_ADMIN_CMD_HEAD, + .tail = INTELXLVF_ADMIN_CMD_TAIL, +}; + +/** Admin event queue register offsets */ +static const struct intelxl_admin_offsets intelxlvf_admin_event_offsets = { + .bal = INTELXLVF_ADMIN_EVT_BAL, + .bah = INTELXLVF_ADMIN_EVT_BAH, + .len = INTELXLVF_ADMIN_EVT_LEN, + .head = INTELXLVF_ADMIN_EVT_HEAD, + .tail = INTELXLVF_ADMIN_EVT_TAIL, +}; + +/** + * Issue admin queue virtual function command + * + * @v netdev Network device + * @ret rc Return status code + */ +static int intelxlvf_admin_command ( struct net_device *netdev ) { + struct intelxl_nic *intelxl = netdev->priv; + struct intelxl_admin *admin = &intelxl->command; + struct intelxl_admin_descriptor *cmd; + unsigned int i; + int rc; + + /* Populate descriptor */ + cmd = &admin->desc[ admin->index % INTELXL_ADMIN_NUM_DESC ]; + cmd->opcode = cpu_to_le16 ( INTELXL_ADMIN_SEND_TO_PF ); + + /* Record opcode */ + intelxl->vopcode = le32_to_cpu ( cmd->vopcode ); + + /* Issue command */ + if ( ( rc = intelxl_admin_command ( intelxl ) ) != 0 ) + goto err_command; + + /* Wait for response */ + for ( i = 0 ; i < INTELXLVF_ADMIN_MAX_WAIT_MS ; i++ ) { + + /* Poll admin event queue */ + intelxl_poll_admin ( netdev ); + + /* If response has not arrived, delay 1ms and retry */ + if ( intelxl->vopcode ) { + mdelay ( 1 ); + continue; + } + + /* Check for errors */ + if ( intelxl->vret != 0 ) + return -EIO; + + return 0; + } + + rc = -ETIMEDOUT; + DBGC ( intelxl, "INTELXL %p timed out waiting for admin VF command " + "%#x\n", intelxl, intelxl->vopcode ); + err_command: + intelxl->vopcode = 0; + return rc; +} + +/** + * Handle link status event + * + * @v netdev Network device + * @v link Link status + */ +static void intelxlvf_admin_link ( struct net_device *netdev, + struct intelxl_admin_vf_status_link *link ) { + struct intelxl_nic *intelxl = netdev->priv; + + DBGC ( intelxl, "INTELXL %p link %#02x speed %#02x\n", intelxl, + link->status, link->speed ); + + /* Update network device */ + if ( link->status ) { + netdev_link_up ( netdev ); + } else { + netdev_link_down ( netdev ); + } +} + +/** + * Handle status change event + * + * @v netdev Network device + * @v stat Status change event + */ +static void +intelxlvf_admin_status ( struct net_device *netdev, + struct intelxl_admin_vf_status_buffer *stat ) { + struct intelxl_nic *intelxl = netdev->priv; + + /* Handle event */ + switch ( stat->event ) { + case cpu_to_le32 ( INTELXL_ADMIN_VF_STATUS_LINK ): + intelxlvf_admin_link ( netdev, &stat->data.link ); + break; + default: + DBGC ( intelxl, "INTELXL %p unrecognised status change " + "event %#x:\n", intelxl, le32_to_cpu ( stat->event ) ); + DBGC_HDA ( intelxl, 0, stat, sizeof ( *stat ) ); + break; + } +} + +/** + * Handle virtual function event + * + * @v netdev Network device + * @v evt Admin queue event descriptor + * @v buf Admin queue event data buffer + */ +void intelxlvf_admin_event ( struct net_device *netdev, + struct intelxl_admin_descriptor *evt, + union intelxl_admin_buffer *buf ) { + struct intelxl_nic *intelxl = netdev->priv; + unsigned int vopcode = le32_to_cpu ( evt->vopcode ); + + /* Record command response if applicable */ + if ( vopcode == intelxl->vopcode ) { + memcpy ( &intelxl->vbuf, buf, sizeof ( intelxl->vbuf ) ); + intelxl->vopcode = 0; + intelxl->vret = le32_to_cpu ( evt->vret ); + if ( intelxl->vret != 0 ) { + DBGC ( intelxl, "INTELXL %p admin VF command %#x " + "error %d\n", intelxl, vopcode, intelxl->vret ); + DBGC_HDA ( intelxl, virt_to_bus ( evt ), evt, + sizeof ( *evt ) ); + DBGC_HDA ( intelxl, virt_to_bus ( buf ), buf, + le16_to_cpu ( evt->len ) ); + } + return; + } + + /* Handle unsolicited events */ + switch ( vopcode ) { + case INTELXL_ADMIN_VF_STATUS: + intelxlvf_admin_status ( netdev, &buf->stat ); + break; + default: + DBGC ( intelxl, "INTELXL %p unrecognised VF event %#x:\n", + intelxl, vopcode ); + DBGC_HDA ( intelxl, 0, evt, sizeof ( *evt ) ); + DBGC_HDA ( intelxl, 0, buf, le16_to_cpu ( evt->len ) ); + break; + } +} + +/** + * Get resources + * + * @v netdev Network device + * @ret rc Return status code + */ +static int intelxlvf_admin_get_resources ( struct net_device *netdev ) { + struct intelxl_nic *intelxl = netdev->priv; + struct intelxl_admin_descriptor *cmd; + struct intelxl_admin_vf_get_resources_buffer *res; + int rc; + + /* Populate descriptor */ + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_GET_RESOURCES ); + + /* Issue command */ + if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) + return rc; + + /* Parse response */ + res = &intelxl->vbuf.res; + intelxl->vsi = le16_to_cpu ( res->vsi ); + memcpy ( netdev->hw_addr, res->mac, ETH_ALEN ); + DBGC ( intelxl, "INTELXL %p VSI %#04x\n", intelxl, intelxl->vsi ); + + return 0; +} + +/****************************************************************************** + * + * Network device interface + * + ****************************************************************************** + */ + +/** + * Configure queues + * + * @v netdev Network device + * @ret rc Return status code + */ +static int intelxlvf_admin_configure ( struct net_device *netdev ) { + struct intelxl_nic *intelxl = netdev->priv; + struct intelxl_admin_descriptor *cmd; + union intelxl_admin_buffer *buf; + int rc; + + /* Populate descriptor */ + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_CONFIGURE ); + cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); + cmd->len = cpu_to_le16 ( sizeof ( buf->cfg ) ); + buf = intelxl_admin_command_buffer ( intelxl ); + buf->cfg.vsi = cpu_to_le16 ( intelxl->vsi ); + buf->cfg.count = cpu_to_le16 ( 1 ); + buf->cfg.tx.vsi = cpu_to_le16 ( intelxl->vsi ); + buf->cfg.tx.count = cpu_to_le16 ( INTELXL_TX_NUM_DESC ); + buf->cfg.tx.base = cpu_to_le64 ( virt_to_bus ( intelxl->tx.desc.raw ) ); + buf->cfg.rx.vsi = cpu_to_le16 ( intelxl->vsi ); + buf->cfg.rx.count = cpu_to_le32 ( INTELXL_RX_NUM_DESC ); + buf->cfg.rx.len = cpu_to_le32 ( intelxl->mfs ); + buf->cfg.rx.mfs = cpu_to_le32 ( intelxl->mfs ); + buf->cfg.rx.base = cpu_to_le64 ( virt_to_bus ( intelxl->rx.desc.raw ) ); + + /* Issue command */ + if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Configure IRQ mapping + * + * @v netdev Network device + * @ret rc Return status code + */ +static int intelxlvf_admin_irq_map ( struct net_device *netdev ) { + struct intelxl_nic *intelxl = netdev->priv; + struct intelxl_admin_descriptor *cmd; + union intelxl_admin_buffer *buf; + int rc; + + /* Populate descriptor */ + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_IRQ_MAP ); + cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); + cmd->len = cpu_to_le16 ( sizeof ( buf->irq ) ); + buf = intelxl_admin_command_buffer ( intelxl ); + buf->irq.count = cpu_to_le16 ( 1 ); + buf->irq.vsi = cpu_to_le16 ( intelxl->vsi ); + buf->irq.rxmap = cpu_to_le16 ( 0x0001 ); + buf->irq.txmap = cpu_to_le16 ( 0x0001 ); + + /* Issue command */ + if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Enable/disable queues + * + * @v netdev Network device + * @v enable Enable queues + * @ret rc Return status code + */ +static int intelxlvf_admin_queues ( struct net_device *netdev, int enable ) { + struct intelxl_nic *intelxl = netdev->priv; + struct intelxl_admin_descriptor *cmd; + union intelxl_admin_buffer *buf; + int rc; + + /* Populate descriptor */ + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->vopcode = ( enable ? cpu_to_le32 ( INTELXL_ADMIN_VF_ENABLE ) : + cpu_to_le32 ( INTELXL_ADMIN_VF_DISABLE ) ); + cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); + cmd->len = cpu_to_le16 ( sizeof ( buf->queues ) ); + buf = intelxl_admin_command_buffer ( intelxl ); + buf->queues.vsi = cpu_to_le16 ( intelxl->vsi ); + buf->queues.rx = cpu_to_le32 ( 1 ); + buf->queues.tx = cpu_to_le32 ( 1 ); + + /* Issue command */ + if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Configure promiscuous mode + * + * @v netdev Network device + * @ret rc Return status code + */ +static int intelxlvf_admin_promisc ( struct net_device *netdev ) { + struct intelxl_nic *intelxl = netdev->priv; + struct intelxl_admin_descriptor *cmd; + union intelxl_admin_buffer *buf; + int rc; + + /* Populate descriptor */ + cmd = intelxl_admin_command_descriptor ( intelxl ); + cmd->vopcode = cpu_to_le32 ( INTELXL_ADMIN_VF_PROMISC ); + cmd->flags = cpu_to_le16 ( INTELXL_ADMIN_FL_RD | INTELXL_ADMIN_FL_BUF ); + cmd->len = cpu_to_le16 ( sizeof ( buf->promisc ) ); + buf = intelxl_admin_command_buffer ( intelxl ); + buf->promisc.vsi = cpu_to_le16 ( intelxl->vsi ); + buf->promisc.flags = cpu_to_le16 ( INTELXL_ADMIN_PROMISC_FL_UNICAST | + INTELXL_ADMIN_PROMISC_FL_MULTICAST ); + + /* Issue command */ + if ( ( rc = intelxlvf_admin_command ( netdev ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int intelxlvf_open ( struct net_device *netdev ) { + struct intelxl_nic *intelxl = netdev->priv; + int rc; + + /* Calculate maximum frame size */ + intelxl->mfs = ( ( ETH_HLEN + netdev->mtu + 4 /* CRC */ + + INTELXL_ALIGN - 1 ) & ~( INTELXL_ALIGN - 1 ) ); + + /* Allocate transmit descriptor ring */ + if ( ( rc = intelxl_alloc_ring ( intelxl, &intelxl->tx ) ) != 0 ) + goto err_alloc_tx; + + /* Allocate receive descriptor ring */ + if ( ( rc = intelxl_alloc_ring ( intelxl, &intelxl->rx ) ) != 0 ) + goto err_alloc_rx; + + /* Configure queues */ + if ( ( rc = intelxlvf_admin_configure ( netdev ) ) != 0 ) + goto err_configure; + + /* Configure IRQ map */ + if ( ( rc = intelxlvf_admin_irq_map ( netdev ) ) != 0 ) + goto err_irq_map; + + /* Enable queues */ + if ( ( rc = intelxlvf_admin_queues ( netdev, 1 ) ) != 0 ) + goto err_enable; + + /* Configure promiscuous mode */ + if ( ( rc = intelxlvf_admin_promisc ( netdev ) ) != 0 ) + goto err_promisc; + + return 0; + + err_promisc: + intelxlvf_admin_queues ( netdev, INTELXL_ADMIN_VF_DISABLE ); + err_enable: + err_irq_map: + err_configure: + intelxl_free_ring ( intelxl, &intelxl->rx ); + err_alloc_rx: + intelxl_free_ring ( intelxl, &intelxl->tx ); + err_alloc_tx: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void intelxlvf_close ( struct net_device *netdev ) { + struct intelxl_nic *intelxl = netdev->priv; + int rc; + + /* Disable queues */ + if ( ( rc = intelxlvf_admin_queues ( netdev, 0 ) ) != 0 ) { + /* Leak memory; there's nothing else we can do */ + return; + } + + /* Free receive descriptor ring */ + intelxl_free_ring ( intelxl, &intelxl->rx ); + + /* Free transmit descriptor ring */ + intelxl_free_ring ( intelxl, &intelxl->tx ); + + /* Discard any unused receive buffers */ + intelxl_empty_rx ( intelxl ); +} + +/** Network device operations */ +static struct net_device_operations intelxlvf_operations = { + .open = intelxlvf_open, + .close = intelxlvf_close, + .transmit = intelxl_transmit, + .poll = intelxl_poll, +}; + +/****************************************************************************** + * + * PCI interface + * + ****************************************************************************** + */ + +/** + * Probe PCI device + * + * @v pci PCI device + * @ret rc Return status code + */ +static int intelxlvf_probe ( struct pci_device *pci ) { + struct net_device *netdev; + struct intelxl_nic *intelxl; + int rc; + + /* Allocate and initialise net device */ + netdev = alloc_etherdev ( sizeof ( *intelxl ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &intelxlvf_operations ); + intelxl = netdev->priv; + pci_set_drvdata ( pci, netdev ); + netdev->dev = &pci->dev; + memset ( intelxl, 0, sizeof ( *intelxl ) ); + intelxl->intr = INTELXLVF_VFINT_DYN_CTL0; + intelxl_init_admin ( &intelxl->command, INTELXLVF_ADMIN, + &intelxlvf_admin_command_offsets ); + intelxl_init_admin ( &intelxl->event, INTELXLVF_ADMIN, + &intelxlvf_admin_event_offsets ); + intelxlvf_init_ring ( &intelxl->tx, INTELXL_TX_NUM_DESC, + sizeof ( intelxl->tx.desc.tx[0] ), + INTELXLVF_QTX_TAIL ); + intelxlvf_init_ring ( &intelxl->rx, INTELXL_RX_NUM_DESC, + sizeof ( intelxl->rx.desc.rx[0] ), + INTELXLVF_QRX_TAIL ); + + /* Fix up PCI device */ + adjust_pci_device ( pci ); + + /* Map registers */ + intelxl->regs = ioremap ( pci->membase, INTELXLVF_BAR_SIZE ); + if ( ! intelxl->regs ) { + rc = -ENODEV; + goto err_ioremap; + } + + /* Locate PCI Express capability */ + intelxl->exp = pci_find_capability ( pci, PCI_CAP_ID_EXP ); + if ( ! intelxl->exp ) { + DBGC ( intelxl, "INTELXL %p missing PCIe capability\n", + intelxl ); + rc = -ENXIO; + goto err_exp; + } + + /* Reset the function via PCIe FLR */ + intelxlvf_reset_flr ( intelxl, pci ); + + /* Enable MSI-X dummy interrupt */ + if ( ( rc = intelxl_msix_enable ( intelxl, pci ) ) != 0 ) + goto err_msix; + + /* Open admin queues */ + if ( ( rc = intelxl_open_admin ( intelxl ) ) != 0 ) + goto err_open_admin; + + /* Reset the function via admin queue */ + if ( ( rc = intelxlvf_reset_admin ( intelxl ) ) != 0 ) + goto err_reset_admin; + + /* Get MAC address */ + if ( ( rc = intelxlvf_admin_get_resources ( netdev ) ) != 0 ) + goto err_get_resources; + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register_netdev; + + return 0; + + unregister_netdev ( netdev ); + err_register_netdev: + err_get_resources: + err_reset_admin: + intelxl_close_admin ( intelxl ); + err_open_admin: + intelxl_msix_disable ( intelxl, pci ); + err_msix: + intelxlvf_reset_flr ( intelxl, pci ); + err_exp: + iounmap ( intelxl->regs ); + err_ioremap: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Remove PCI device + * + * @v pci PCI device + */ +static void intelxlvf_remove ( struct pci_device *pci ) { + struct net_device *netdev = pci_get_drvdata ( pci ); + struct intelxl_nic *intelxl = netdev->priv; + + /* Unregister network device */ + unregister_netdev ( netdev ); + + /* Reset the function via admin queue */ + intelxlvf_reset_admin ( intelxl ); + + /* Close admin queues */ + intelxl_close_admin ( intelxl ); + + /* Disable MSI-X dummy interrupt */ + intelxl_msix_disable ( intelxl, pci ); + + /* Reset the function via PCIe FLR */ + intelxlvf_reset_flr ( intelxl, pci ); + + /* Free network device */ + iounmap ( intelxl->regs ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} + +/** PCI device IDs */ +static struct pci_device_id intelxlvf_nics[] = { + PCI_ROM ( 0x8086, 0x154c, "xl710-vf", "XL710 VF", 0 ), + PCI_ROM ( 0x8086, 0x1571, "xl710-vf-hv", "XL710 VF (Hyper-V)", 0 ), + PCI_ROM ( 0x8086, 0x1889, "xl710-vf-ad", "XL710 VF (adaptive)", 0 ), + PCI_ROM ( 0x8086, 0x37cd, "x722-vf", "X722 VF", 0 ), + PCI_ROM ( 0x8086, 0x37d9, "x722-vf-hv", "X722 VF (Hyper-V)", 0 ), +}; + +/** PCI driver */ +struct pci_driver intelxlvf_driver __pci_driver = { + .ids = intelxlvf_nics, + .id_count = ( sizeof ( intelxlvf_nics ) / + sizeof ( intelxlvf_nics[0] ) ), + .probe = intelxlvf_probe, + .remove = intelxlvf_remove, +}; diff --git a/src/drivers/net/intelxlvf.h b/src/drivers/net/intelxlvf.h new file mode 100644 index 000000000..ffcae5674 --- /dev/null +++ b/src/drivers/net/intelxlvf.h @@ -0,0 +1,86 @@ +#ifndef _INTELXLVF_H +#define _INTELXLVF_H + +/** @file + * + * Intel 40 Gigabit Ethernet virtual function network card driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include "intelxl.h" + +/** BAR size */ +#define INTELXLVF_BAR_SIZE 0x10000 + +/** Transmit Queue Tail Register */ +#define INTELXLVF_QTX_TAIL 0x00000 + +/** Receive Queue Tail Register */ +#define INTELXLVF_QRX_TAIL 0x02000 + +/** VF Interrupt Zero Dynamic Control Register */ +#define INTELXLVF_VFINT_DYN_CTL0 0x5c00 + +/** VF Admin Queue register block */ +#define INTELXLVF_ADMIN 0x6000 + +/** Admin Command Queue Base Address Low Register (offset) */ +#define INTELXLVF_ADMIN_CMD_BAL 0x1c00 + +/** Admin Command Queue Base Address High Register (offset) */ +#define INTELXLVF_ADMIN_CMD_BAH 0x1800 + +/** Admin Command Queue Length Register (offset) */ +#define INTELXLVF_ADMIN_CMD_LEN 0x0800 + +/** Admin Command Queue Head Register (offset) */ +#define INTELXLVF_ADMIN_CMD_HEAD 0x0400 + +/** Admin Command Queue Tail Register (offset) */ +#define INTELXLVF_ADMIN_CMD_TAIL 0x2400 + +/** Admin Event Queue Base Address Low Register (offset) */ +#define INTELXLVF_ADMIN_EVT_BAL 0x0c00 + +/** Admin Event Queue Base Address High Register (offset) */ +#define INTELXLVF_ADMIN_EVT_BAH 0x0000 + +/** Admin Event Queue Length Register (offset) */ +#define INTELXLVF_ADMIN_EVT_LEN 0x2000 + +/** Admin Event Queue Head Register (offset) */ +#define INTELXLVF_ADMIN_EVT_HEAD 0x1400 + +/** Admin Event Queue Tail Register (offset) */ +#define INTELXLVF_ADMIN_EVT_TAIL 0x1000 + +/** Maximum time to wait for a VF admin request to complete */ +#define INTELXLVF_ADMIN_MAX_WAIT_MS 2000 + +/** VF Reset Status Register */ +#define INTELXLVF_VFGEN_RSTAT 0x8800 +#define INTELXLVF_VFGEN_RSTAT_VFR_STATE(x) ( (x) & 0x3 ) +#define INTELXLVF_VFGEN_RSTAT_VFR_STATE_ACTIVE 0x2 + +/** Maximum time to wait for reset to complete */ +#define INTELXLVF_RESET_MAX_WAIT_MS 1000 + +/** + * Initialise descriptor ring + * + * @v ring Descriptor ring + * @v count Number of descriptors + * @v len Length of a single descriptor + * @v tail Tail register offset + */ +static inline __attribute__ (( always_inline)) void +intelxlvf_init_ring ( struct intelxl_ring *ring, unsigned int count, + size_t len, unsigned int tail ) { + + ring->len = ( count * len ); + ring->tail = tail; +} + +#endif /* _INTELXLVF_H */ diff --git a/src/drivers/net/lan78xx.c b/src/drivers/net/lan78xx.c index 3f705203e..a8cf57408 100644 --- a/src/drivers/net/lan78xx.c +++ b/src/drivers/net/lan78xx.c @@ -97,6 +97,10 @@ static int lan78xx_fetch_mac ( struct smscusb_device *smscusb ) { if ( ( rc = smscusb_otp_fetch_mac ( smscusb, LAN78XX_OTP_BASE ) ) == 0 ) return 0; + /* Read MAC address from device tree, if present */ + if ( ( rc = smscusb_fdt_fetch_mac ( smscusb ) ) == 0 ) + return 0; + /* Otherwise, generate a random MAC address */ eth_random_addr ( netdev->hw_addr ); DBGC ( smscusb, "LAN78XX %p using random MAC %s\n", diff --git a/src/drivers/net/smsc95xx.c b/src/drivers/net/smsc95xx.c index 9b09657db..3ec49584d 100644 --- a/src/drivers/net/smsc95xx.c +++ b/src/drivers/net/smsc95xx.c @@ -173,6 +173,10 @@ static int smsc95xx_fetch_mac ( struct smscusb_device *smscusb ) { SMSC95XX_E2P_BASE ) ) == 0 ) return 0; + /* Read MAC address from device tree, if present */ + if ( ( rc = smscusb_fdt_fetch_mac ( smscusb ) ) == 0 ) + return 0; + /* Construct MAC address for Honeywell VM3, if applicable */ if ( ( rc = smsc95xx_vm3_fetch_mac ( smscusb ) ) == 0 ) return 0; diff --git a/src/drivers/net/smscusb.c b/src/drivers/net/smscusb.c index 538d338c4..c639c58c1 100644 --- a/src/drivers/net/smscusb.c +++ b/src/drivers/net/smscusb.c @@ -30,6 +30,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/usbnet.h> #include <ipxe/ethernet.h> #include <ipxe/profile.h> +#include <ipxe/fdt.h> #include "smscusb.h" /** @file @@ -441,6 +442,39 @@ int smscusb_otp_fetch_mac ( struct smscusb_device *smscusb, /****************************************************************************** * + * Device tree + * + ****************************************************************************** + */ + +/** + * Fetch MAC address from device tree + * + * @v smscusb SMSC USB device + * @ret rc Return status code + */ +int smscusb_fdt_fetch_mac ( struct smscusb_device *smscusb ) { + struct net_device *netdev = smscusb->netdev; + unsigned int offset; + int rc; + + /* Look for "ethernet[0]" alias */ + if ( ( rc = fdt_alias ( "ethernet", &offset ) != 0 ) && + ( rc = fdt_alias ( "ethernet0", &offset ) != 0 ) ) { + return rc; + } + + /* Fetch MAC address */ + if ( ( rc = fdt_mac ( offset, netdev ) ) != 0 ) + return rc; + + DBGC ( smscusb, "SMSCUSB %p using FDT MAC %s\n", + smscusb, eth_ntoa ( netdev->hw_addr ) ); + return 0; +} + +/****************************************************************************** + * * MII access * ****************************************************************************** diff --git a/src/drivers/net/smscusb.h b/src/drivers/net/smscusb.h index b5d9ad3fc..e866bb747 100644 --- a/src/drivers/net/smscusb.h +++ b/src/drivers/net/smscusb.h @@ -287,6 +287,7 @@ extern int smscusb_eeprom_fetch_mac ( struct smscusb_device *smscusb, unsigned int e2p_base ); extern int smscusb_otp_fetch_mac ( struct smscusb_device *smscusb, unsigned int otp_base ); +extern int smscusb_fdt_fetch_mac ( struct smscusb_device *smscusb ); extern int smscusb_mii_check_link ( struct smscusb_device *smscusb ); extern int smscusb_mii_open ( struct smscusb_device *smscusb, unsigned int phy_mask, unsigned int intrs ); diff --git a/src/include/errno.h b/src/include/errno.h index e80bf9ca5..342384fa4 100644 --- a/src/include/errno.h +++ b/src/include/errno.h @@ -262,10 +262,10 @@ static inline void eplatform_discard ( int dummy __unused, ... ) {} ".align 8\n\t" \ "\n1:\n\t" \ ".long ( 4f - 1b )\n\t" \ - ".long %a0\n\t" \ + ".long %c0\n\t" \ ".long ( 2f - 1b )\n\t" \ ".long ( 3f - 1b )\n\t" \ - ".long %a1\n\t" \ + ".long %c1\n\t" \ "\n2:\t.asciz \"" __einfo_desc ( einfo ) "\"\n\t" \ "\n3:\t.asciz \"" __FILE__ "\"\n\t" \ ".align 8\n\t" \ diff --git a/src/include/ipxe/efi/efi_blacklist.h b/src/include/ipxe/efi/efi_blacklist.h new file mode 100644 index 000000000..c5a5a61dc --- /dev/null +++ b/src/include/ipxe/efi/efi_blacklist.h @@ -0,0 +1,13 @@ +#ifndef _IPXE_EFI_BLACKLIST_H +#define _IPXE_EFI_BLACKLIST_H + +/** @file + * + * EFI driver blacklist + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern void efi_unload_blacklist ( void ); + +#endif /* _IPXE_EFI_BLACKLIST_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 596491a1f..242f91f82 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -74,6 +74,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_block ( ERRFILE_CORE | 0x00220000 ) #define ERRFILE_sanboot ( ERRFILE_CORE | 0x00230000 ) #define ERRFILE_dummy_sanboot ( ERRFILE_CORE | 0x00240000 ) +#define ERRFILE_fdt ( ERRFILE_CORE | 0x00250000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) @@ -205,6 +206,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_ena ( ERRFILE_DRIVER | 0x00c90000 ) #define ERRFILE_icplus ( ERRFILE_DRIVER | 0x00ca0000 ) #define ERRFILE_intelxl ( ERRFILE_DRIVER | 0x00cb0000 ) +#define ERRFILE_pcimsix ( ERRFILE_DRIVER | 0x00cc0000 ) +#define ERRFILE_intelxlvf ( ERRFILE_DRIVER | 0x00cd0000 ) #define ERRFILE_aoe ( ERRFILE_NET | 0x00000000 ) #define ERRFILE_arp ( ERRFILE_NET | 0x00010000 ) @@ -375,6 +378,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_cert_cmd ( ERRFILE_OTHER | 0x004f0000 ) #define ERRFILE_acpi_settings ( ERRFILE_OTHER | 0x00500000 ) #define ERRFILE_ntlm ( ERRFILE_OTHER | 0x00510000 ) +#define ERRFILE_efi_blacklist ( ERRFILE_OTHER | 0x00520000 ) /** @} */ diff --git a/src/include/ipxe/fdt.h b/src/include/ipxe/fdt.h new file mode 100644 index 000000000..97efa100c --- /dev/null +++ b/src/include/ipxe/fdt.h @@ -0,0 +1,102 @@ +#ifndef _IPXE_FDT_H +#define _IPXE_FDT_H + +/** @file + * + * Flattened Device Tree + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +struct net_device; + +/** Device tree header */ +struct fdt_header { + /** Magic signature */ + uint32_t magic; + /** Total size of device tree */ + uint32_t totalsize; + /** Offset to structure block */ + uint32_t off_dt_struct; + /** Offset to strings block */ + uint32_t off_dt_strings; + /** Offset to memory reservation block */ + uint32_t off_mem_rsvmap; + /** Version of this data structure */ + uint32_t version; + /** Lowest version to which this structure is compatible */ + uint32_t last_comp_version; + /** Physical ID of the boot CPU */ + uint32_t boot_cpuid_phys; + /** Length of string block */ + uint32_t size_dt_strings; + /** Length of structure block */ + uint32_t size_dt_struct; +} __attribute__ (( packed )); + +/** Magic signature */ +#define FDT_MAGIC 0xd00dfeed + +/** Expected device tree version */ +#define FDT_VERSION 16 + +/** Device tree token */ +typedef uint32_t fdt_token_t; + +/** Begin node token */ +#define FDT_BEGIN_NODE 0x00000001 + +/** End node token */ +#define FDT_END_NODE 0x00000002 + +/** Property token */ +#define FDT_PROP 0x00000003 + +/** Property fragment */ +struct fdt_prop { + /** Data length */ + uint32_t len; + /** Name offset */ + uint32_t name_off; +} __attribute__ (( packed )); + +/** NOP token */ +#define FDT_NOP 0x00000004 + +/** End of structure block */ +#define FDT_END 0x00000009 + +/** Alignment of structure block */ +#define FDT_STRUCTURE_ALIGN ( sizeof ( fdt_token_t ) ) + +/** A device tree */ +struct fdt { + /** Tree data */ + union { + /** Tree header */ + const struct fdt_header *hdr; + /** Raw data */ + const void *raw; + }; + /** Length of tree */ + size_t len; + /** Offset to structure block */ + unsigned int structure; + /** Length of structure block */ + size_t structure_len; + /** Offset to strings block */ + unsigned int strings; + /** Length of strings block */ + size_t strings_len; +}; + +extern int fdt_path ( const char *path, unsigned int *offset ); +extern int fdt_alias ( const char *name, unsigned int *offset ); +extern const char * fdt_string ( unsigned int offset, const char *name ); +extern int fdt_mac ( unsigned int offset, struct net_device *netdev ); +extern int register_fdt ( const struct fdt_header *hdr ); + +#endif /* _IPXE_FDT_H */ diff --git a/src/include/ipxe/ocsp.h b/src/include/ipxe/ocsp.h index be0bddc50..9eb70b2cc 100644 --- a/src/include/ipxe/ocsp.h +++ b/src/include/ipxe/ocsp.h @@ -42,8 +42,8 @@ struct ocsp_check; struct ocsp_request { /** Request builder */ struct asn1_builder builder; - /** Certificate ID */ - struct asn1_cursor cert_id; + /** Certificate ID (excluding hashAlgorithm) */ + struct asn1_cursor cert_id_tail; }; /** An OCSP responder */ diff --git a/src/include/ipxe/pci.h b/src/include/ipxe/pci.h index ddd8c8d1e..272c4c06f 100644 --- a/src/include/ipxe/pci.h +++ b/src/include/ipxe/pci.h @@ -94,6 +94,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define PCI_CAP_ID_VPD 0x03 /**< Vital product data */ #define PCI_CAP_ID_VNDR 0x09 /**< Vendor-specific */ #define PCI_CAP_ID_EXP 0x10 /**< PCI Express */ +#define PCI_CAP_ID_MSIX 0x11 /**< MSI-X */ #define PCI_CAP_ID_EA 0x14 /**< Enhanced Allocation */ /** Next capability */ @@ -109,6 +110,16 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define PCI_EXP_DEVCTL 0x08 #define PCI_EXP_DEVCTL_FLR 0x8000 /**< Function level reset */ +/** MSI-X interrupts */ +#define PCI_MSIX_CTRL 0x02 +#define PCI_MSIX_CTRL_ENABLE 0x8000 /**< Enable MSI-X */ +#define PCI_MSIX_CTRL_MASK 0x4000 /**< Mask all interrupts */ +#define PCI_MSIX_CTRL_SIZE(x) ( (x) & 0x07ff ) /**< Table size */ +#define PCI_MSIX_DESC_TABLE 0x04 +#define PCI_MSIX_DESC_PBA 0x08 +#define PCI_MSIX_DESC_BIR(x) ( (x) & 0x00000007 ) /**< BAR index */ +#define PCI_MSIX_DESC_OFFSET(x) ( (x) & 0xfffffff8 ) /**< BAR offset */ + /** Uncorrectable error status */ #define PCI_ERR_UNCOR_STATUS 0x04 diff --git a/src/include/ipxe/pcimsix.h b/src/include/ipxe/pcimsix.h new file mode 100644 index 000000000..aa2aaf017 --- /dev/null +++ b/src/include/ipxe/pcimsix.h @@ -0,0 +1,77 @@ +#ifndef _IPXE_PCIMSIX_H +#define _IPXE_PCIMSIX_H + +/** @file + * + * PCI MSI-X interrupts + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <ipxe/pci.h> + +/** MSI-X BAR mapped length */ +#define PCI_MSIX_LEN 0x1000 + +/** MSI-X vector offset */ +#define PCI_MSIX_VECTOR(n) ( (n) * 0x10 ) + +/** MSI-X vector address low 32 bits */ +#define PCI_MSIX_ADDRESS_LO 0x0 + +/** MSI-X vector address high 32 bits */ +#define PCI_MSIX_ADDRESS_HI 0x4 + +/** MSI-X vector data */ +#define PCI_MSIX_DATA 0x8 + +/** MSI-X vector control */ +#define PCI_MSIX_CONTROL 0xc +#define PCI_MSIX_CONTROL_MASK 0x00000001 /**< Vector is masked */ + +/** PCI MSI-X capability */ +struct pci_msix { + /** Capability offset */ + unsigned int cap; + /** Number of vectors */ + unsigned int count; + /** MSI-X table */ + void *table; + /** Pending bit array */ + void *pba; +}; + +extern int pci_msix_enable ( struct pci_device *pci, struct pci_msix *msix ); +extern void pci_msix_disable ( struct pci_device *pci, struct pci_msix *msix ); +extern void pci_msix_map ( struct pci_msix *msix, unsigned int vector, + physaddr_t address, uint32_t data ); +extern void pci_msix_control ( struct pci_msix *msix, unsigned int vector, + uint32_t mask ); +extern void pci_msix_dump ( struct pci_msix *msix, unsigned int vector ); + +/** + * Mask MSI-X interrupt vector + * + * @v msix MSI-X capability + * @v vector MSI-X vector + */ +static inline __attribute__ (( always_inline )) void +pci_msix_mask ( struct pci_msix *msix, unsigned int vector ) { + + pci_msix_control ( msix, vector, PCI_MSIX_CONTROL_MASK ); +} + +/** + * Unmask MSI-X interrupt vector + * + * @v msix MSI-X capability + * @v vector MSI-X vector + */ +static inline __attribute__ (( always_inline )) void +pci_msix_unmask ( struct pci_msix *msix, unsigned int vector ) { + + pci_msix_control ( msix, vector, 0 ); +} + +#endif /* _IPXE_PCIMSIX_H */ diff --git a/src/include/ipxe/tls.h b/src/include/ipxe/tls.h index b1e702e18..febbdc589 100644 --- a/src/include/ipxe/tls.h +++ b/src/include/ipxe/tls.h @@ -63,6 +63,7 @@ struct tls_header { #define TLS_HELLO_REQUEST 0 #define TLS_CLIENT_HELLO 1 #define TLS_SERVER_HELLO 2 +#define TLS_NEW_SESSION_TICKET 4 #define TLS_CERTIFICATE 11 #define TLS_SERVER_KEY_EXCHANGE 12 #define TLS_CERTIFICATE_REQUEST 13 @@ -108,6 +109,9 @@ struct tls_header { /* TLS signature algorithms extension */ #define TLS_SIGNATURE_ALGORITHMS 13 +/* TLS session ticket extension */ +#define TLS_SESSION_TICKET 35 + /* TLS renegotiation information extension */ #define TLS_RENEGOTIATION_INFO 0xff01 @@ -242,13 +246,48 @@ struct md5_sha1_digest { /** MD5+SHA1 digest size */ #define MD5_SHA1_DIGEST_SIZE sizeof ( struct md5_sha1_digest ) -/** A TLS connection */ -struct tls_connection { +/** A TLS session */ +struct tls_session { /** Reference counter */ struct refcnt refcnt; + /** List of sessions */ + struct list_head list; /** Server name */ const char *name; + /** Session ID */ + uint8_t id[32]; + /** Length of session ID */ + size_t id_len; + /** Session ticket */ + void *ticket; + /** Length of session ticket */ + size_t ticket_len; + /** Master secret */ + uint8_t master_secret[48]; + + /** List of connections */ + struct list_head conn; +}; + +/** A TLS connection */ +struct tls_connection { + /** Reference counter */ + struct refcnt refcnt; + + /** Session */ + struct tls_session *session; + /** List of connections within the same session */ + struct list_head list; + /** Session ID */ + uint8_t session_id[32]; + /** Length of session ID */ + size_t session_id_len; + /** New session ticket */ + void *new_session_ticket; + /** Length of new session ticket */ + size_t new_session_ticket_len; + /** Plaintext stream */ struct interface plainstream; /** Ciphertext stream */ @@ -296,6 +335,8 @@ struct tls_connection { struct pending_operation client_negotiation; /** Server security negotiation pending operation */ struct pending_operation server_negotiation; + /** Certificate validation pending operation */ + struct pending_operation validation; /** TX sequence number */ uint64_t tx_seq; diff --git a/src/include/ipxe/vlan.h b/src/include/ipxe/vlan.h index 439e0c16d..7f93439b3 100644 --- a/src/include/ipxe/vlan.h +++ b/src/include/ipxe/vlan.h @@ -10,6 +10,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +#include <ipxe/netdevice.h> + /** A VLAN header */ struct vlan_header { /** Tag control information */ @@ -59,12 +61,14 @@ struct vlan_header { */ #define VLAN_PRIORITY_IS_VALID( priority ) ( (priority) <= 7 ) -extern struct net_device * vlan_find ( struct net_device *trunk, - unsigned int tag ); extern unsigned int vlan_tag ( struct net_device *netdev ); extern int vlan_can_be_trunk ( struct net_device *trunk ); extern int vlan_create ( struct net_device *trunk, unsigned int tag, unsigned int priority ); extern int vlan_destroy ( struct net_device *netdev ); +extern void vlan_netdev_rx ( struct net_device *netdev, unsigned int tag, + struct io_buffer *iobuf ); +extern void vlan_netdev_rx_err ( struct net_device *netdev, unsigned int tag, + struct io_buffer *iobuf, int rc ); #endif /* _IPXE_VLAN_H */ diff --git a/src/interface/efi/efi_blacklist.c b/src/interface/efi/efi_blacklist.c new file mode 100644 index 000000000..292b28e8c --- /dev/null +++ b/src/interface/efi/efi_blacklist.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2019 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <ipxe/settings.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/DriverBinding.h> +#include <ipxe/efi/Protocol/LoadedImage.h> +#include <ipxe/efi/Protocol/ComponentName.h> +#include <ipxe/efi/efi_blacklist.h> + +/** @file + * + * EFI driver blacklist + * + */ + +/** A blacklisted driver */ +struct efi_blacklist { + /** Name */ + const char *name; + /** + * Check if driver is blacklisted + * + * @v binding Driver binding protocol + * @v loaded Loaded image protocol + * @v wtf Component name protocol, if present + * @ret blacklisted Driver is the blacklisted driver + */ + int ( * blacklist ) ( EFI_DRIVER_BINDING_PROTOCOL *binding, + EFI_LOADED_IMAGE_PROTOCOL *loaded, + EFI_COMPONENT_NAME_PROTOCOL *wtf ); +}; + +/** + * Blacklist Dell Ip4ConfigDxe driver + * + * @v binding Driver binding protocol + * @v loaded Loaded image protocol + * @v wtf Component name protocol, if present + * @ret blacklisted Driver is the blacklisted driver + */ +static int +efi_blacklist_dell_ip4config ( EFI_DRIVER_BINDING_PROTOCOL *binding __unused, + EFI_LOADED_IMAGE_PROTOCOL *loaded __unused, + EFI_COMPONENT_NAME_PROTOCOL *wtf ) { + static const CHAR16 ip4cfg[] = L"IP4 CONFIG Network Service Driver"; + static const char dell[] = "Dell Inc."; + char manufacturer[ sizeof ( dell ) ]; + CHAR16 *name; + + /* Check driver name */ + if ( ! wtf ) + return 0; + if ( wtf->GetDriverName ( wtf, "eng", &name ) != 0 ) + return 0; + if ( memcmp ( name, ip4cfg, sizeof ( ip4cfg ) ) != 0 ) + return 0; + + /* Check manufacturer */ + fetch_string_setting ( NULL, &manufacturer_setting, manufacturer, + sizeof ( manufacturer ) ); + if ( strcmp ( manufacturer, dell ) != 0 ) + return 0; + + return 1; +} + +/** Blacklisted drivers */ +static struct efi_blacklist efi_blacklists[] = { + { + .name = "Dell Ip4Config", + .blacklist = efi_blacklist_dell_ip4config, + }, +}; + +/** + * Find driver blacklisting, if any + * + * @v driver Driver binding handle + * @ret blacklist Driver blacklisting, or NULL + * @ret rc Return status code + */ +static int efi_blacklist ( EFI_HANDLE driver, + struct efi_blacklist **blacklist ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_DRIVER_BINDING_PROTOCOL *binding; + void *interface; + } binding; + union { + EFI_LOADED_IMAGE_PROTOCOL *loaded; + void *interface; + } loaded; + union { + EFI_COMPONENT_NAME_PROTOCOL *wtf; + void *interface; + } wtf; + unsigned int i; + EFI_HANDLE image; + EFI_STATUS efirc; + int rc; + + DBGC2 ( &efi_blacklists, "EFIBL checking %s\n", + efi_handle_name ( driver ) ); + + /* Mark as not blacklisted */ + *blacklist = NULL; + + /* Open driver binding protocol */ + if ( ( efirc = bs->OpenProtocol ( + driver, &efi_driver_binding_protocol_guid, + &binding.interface, efi_image_handle, driver, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( driver, "EFIBL %s could not open driver binding " + "protocol: %s\n", efi_handle_name ( driver ), + strerror ( rc ) ); + goto err_binding; + } + image = binding.binding->ImageHandle; + + /* Open loaded image protocol */ + if ( ( efirc = bs->OpenProtocol ( + image, &efi_loaded_image_protocol_guid, + &loaded.interface, efi_image_handle, image, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( driver, "EFIBL %s could not open", + efi_handle_name ( driver ) ); + DBGC ( driver, " %s loaded image protocol: %s\n", + efi_handle_name ( image ), strerror ( rc ) ); + goto err_loaded; + } + + /* Open component name protocol, if present*/ + if ( ( efirc = bs->OpenProtocol ( + driver, &efi_component_name_protocol_guid, + &wtf.interface, efi_image_handle, driver, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) { + /* Ignore failure; is not required to be present */ + wtf.interface = NULL; + } + + /* Check blacklistings */ + for ( i = 0 ; i < ( sizeof ( efi_blacklists ) / + sizeof ( efi_blacklists[0] ) ) ; i++ ) { + if ( efi_blacklists[i].blacklist ( binding.binding, + loaded.loaded, wtf.wtf ) ) { + *blacklist = &efi_blacklists[i]; + break; + } + } + + /* Success */ + rc = 0; + + /* Close protocols */ + if ( wtf.wtf ) { + bs->CloseProtocol ( driver, &efi_component_name_protocol_guid, + efi_image_handle, driver ); + } + bs->CloseProtocol ( image, &efi_loaded_image_protocol_guid, + efi_image_handle, image ); + err_loaded: + bs->CloseProtocol ( driver, &efi_driver_binding_protocol_guid, + efi_image_handle, driver ); + err_binding: + return rc; +} + +/** + * Unload any blacklisted drivers + * + */ +void efi_unload_blacklist ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_blacklist *blacklist; + EFI_HANDLE *drivers; + EFI_HANDLE driver; + UINTN num_drivers; + unsigned int i; + EFI_STATUS efirc; + int rc; + + /* Locate all driver binding protocol handles */ + if ( ( efirc = bs->LocateHandleBuffer ( + ByProtocol, &efi_driver_binding_protocol_guid, + NULL, &num_drivers, &drivers ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( &efi_blacklists, "EFIBL could not list all drivers: " + "%s\n", strerror ( rc ) ); + return; + } + + /* Unload any blacklisted drivers */ + for ( i = 0 ; i < num_drivers ; i++ ) { + driver = drivers[i]; + if ( ( rc = efi_blacklist ( driver, &blacklist ) ) != 0 ) { + DBGC ( driver, "EFIBL could not determine " + "blacklisting for %s: %s\n", + efi_handle_name ( driver ), strerror ( rc ) ); + continue; + } + if ( ! blacklist ) + continue; + DBGC ( driver, "EFIBL unloading %s (%s)\n", + efi_handle_name ( driver ), blacklist->name ); + if ( ( efirc = bs->UnloadImage ( driver ) ) != 0 ) { + DBGC ( driver, "EFIBL could not unload %s: %s\n", + efi_handle_name ( driver ), strerror ( rc ) ); + } + } + + /* Free handle list */ + bs->FreePool ( drivers ); +} diff --git a/src/interface/efi/efi_debug.c b/src/interface/efi/efi_debug.c index 8ea0a822d..de9b1af55 100644 --- a/src/interface/efi/efi_debug.c +++ b/src/interface/efi/efi_debug.c @@ -331,8 +331,7 @@ void dbg_efi_protocols ( EFI_HANDLE handle ) { /* Sanity check */ if ( ! handle ) { - printf ( "HANDLE %s could not retrieve protocols\n", - efi_handle_name ( handle ) ); + printf ( "HANDLE %p could not retrieve protocols\n", handle ); return; } diff --git a/src/interface/efi/efi_driver.c b/src/interface/efi/efi_driver.c index 04796414a..7be2e585d 100644 --- a/src/interface/efi/efi_driver.c +++ b/src/interface/efi/efi_driver.c @@ -73,11 +73,14 @@ static struct efi_device * efidev_find ( EFI_HANDLE device ) { */ struct efi_device * efidev_parent ( struct device *dev ) { struct device *parent; + struct efi_device *efidev; - /* Walk upwards until we find an EFI device */ + /* Walk upwards until we find a registered EFI device */ while ( ( parent = dev->parent ) ) { - if ( parent->desc.bus_type == BUS_TYPE_EFI ) - return container_of ( parent, struct efi_device, dev ); + list_for_each_entry ( efidev, &efi_devices, dev.siblings ) { + if ( parent == &efidev->dev ) + return efidev; + } dev = parent; } diff --git a/src/interface/efi/efi_fdt.c b/src/interface/efi/efi_fdt.c new file mode 100644 index 000000000..cd3f109df --- /dev/null +++ b/src/interface/efi/efi_fdt.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 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 (at your option) 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 ); + +#include <string.h> +#include <ipxe/fdt.h> +#include <ipxe/efi/efi.h> +#include <ipxe/init.h> + +/** @file + * + * EFI Flattened Device Tree + * + */ + +#define DEVICE_TREE_TABLE_GUID \ + { 0xb1b621d5, 0xf19c, 0x41a5, \ + { 0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0 } } + +/** EFI Flattened Device Tree configuration table */ +static struct fdt_header *efi_fdt; +EFI_USE_TABLE ( DEVICE_TREE_TABLE, &efi_fdt, 0 ); + +/** + * Initialise EFI Flattened Device Tree + * + */ +static void efi_fdt_init ( void ) { + int rc; + + /* Do nothing if no configuration table is present */ + if ( ! efi_fdt ) { + DBGC ( &efi_fdt, "EFIFDT has no configuration table\n" ); + return; + } + DBGC ( &efi_fdt, "EFIFDT configuration table at %p\n", efi_fdt ); + + /* Register device tree */ + if ( ( rc = register_fdt ( efi_fdt ) ) != 0 ) { + DBGC ( &efi_fdt, "EFIFDT could not register: %s\n", + strerror ( rc ) ); + return; + } +} + +/** EFI Flattened Device Tree initialisation function */ +struct init_fn efi_fdt_init_fn __init_fn ( INIT_EARLY ) = { + .initialise = efi_fdt_init, +}; diff --git a/src/interface/efi/efiprefix.c b/src/interface/efi/efiprefix.c index 18b931e68..de3572c75 100644 --- a/src/interface/efi/efiprefix.c +++ b/src/interface/efi/efiprefix.c @@ -27,6 +27,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/efi/efi_snp.h> #include <ipxe/efi/efi_autoboot.h> #include <ipxe/efi/efi_watchdog.h> +#include <ipxe/efi/efi_blacklist.h> /** * EFI entry point @@ -75,6 +76,10 @@ EFI_STATUS EFIAPI _efi_start ( EFI_HANDLE image_handle, */ static int efi_probe ( struct root_device *rootdev __unused ) { + /* Unloaded any blacklisted drivers */ + efi_unload_blacklist(); + + /* Connect our drivers */ return efi_driver_connect_all(); } @@ -85,6 +90,7 @@ static int efi_probe ( struct root_device *rootdev __unused ) { */ static void efi_remove ( struct root_device *rootdev __unused ) { + /* Disconnect our drivers */ efi_driver_disconnect_all(); } diff --git a/src/net/netdevice.c b/src/net/netdevice.c index 71a37eccc..3b02e64bd 100644 --- a/src/net/netdevice.c +++ b/src/net/netdevice.c @@ -1126,15 +1126,35 @@ __weak unsigned int vlan_tag ( struct net_device *netdev __unused ) { } /** - * Identify VLAN device (when VLAN support is not present) + * Add VLAN tag-stripped packet to queue (when VLAN support is not present) * - * @v trunk Trunk network device - * @v tag VLAN tag - * @ret netdev VLAN device, if any + * @v netdev Network device + * @v tag VLAN tag, or zero + * @v iobuf I/O buffer */ -__weak struct net_device * vlan_find ( struct net_device *trunk __unused, - unsigned int tag __unused ) { - return NULL; +__weak void vlan_netdev_rx ( struct net_device *netdev, unsigned int tag, + struct io_buffer *iobuf ) { + + if ( tag == 0 ) { + netdev_rx ( netdev, iobuf ); + } else { + netdev_rx_err ( netdev, iobuf, -ENODEV ); + } +} + +/** + * Discard received VLAN tag-stripped packet (when VLAN support is not present) + * + * @v netdev Network device + * @v tag VLAN tag, or zero + * @v iobuf I/O buffer, or NULL + * @v rc Packet status code + */ +__weak void vlan_netdev_rx_err ( struct net_device *netdev, + unsigned int tag __unused, + struct io_buffer *iobuf, int rc ) { + + netdev_rx_err ( netdev, iobuf, rc ); } /** Networking stack process */ diff --git a/src/net/tcp.c b/src/net/tcp.c index c445100ad..6bba44282 100644 --- a/src/net/tcp.c +++ b/src/net/tcp.c @@ -17,6 +17,7 @@ #include <ipxe/netdevice.h> #include <ipxe/profile.h> #include <ipxe/process.h> +#include <ipxe/job.h> #include <ipxe/tcpip.h> #include <ipxe/tcp.h> @@ -1704,10 +1705,30 @@ static int tcp_xfer_deliver ( struct tcp_connection *tcp, return 0; } +/** + * Report job progress + * + * @v tcp TCP connection + * @v progress Progress report to fill in + * @ret ongoing_rc Ongoing job status code (if known) + */ +static int tcp_progress ( struct tcp_connection *tcp, + struct job_progress *progress ) { + + /* Report connection in progress if applicable */ + if ( ! TCP_HAS_BEEN_ESTABLISHED ( tcp->tcp_state ) ) { + snprintf ( progress->message, sizeof ( progress->message ), + "connecting" ); + } + + return 0; +} + /** TCP data transfer interface operations */ static struct interface_operation tcp_xfer_operations[] = { INTF_OP ( xfer_deliver, struct tcp_connection *, tcp_xfer_deliver ), INTF_OP ( xfer_window, struct tcp_connection *, tcp_xfer_window ), + INTF_OP ( job_progress, struct tcp_connection *, tcp_progress ), INTF_OP ( intf_close, struct tcp_connection *, tcp_xfer_close ), }; diff --git a/src/net/tls.c b/src/net/tls.c index 9d994cd77..746274d61 100644 --- a/src/net/tls.c +++ b/src/net/tls.c @@ -47,6 +47,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/certstore.h> #include <ipxe/rbg.h> #include <ipxe/validator.h> +#include <ipxe/job.h> #include <ipxe/tls.h> /* Disambiguate the various error causes */ @@ -102,9 +103,13 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define EINFO_EINVAL_MAC \ __einfo_uniqify ( EINFO_EINVAL, 0x0d, \ "Invalid MAC" ) +#define EINVAL_TICKET __einfo_error ( EINFO_EINVAL_TICKET ) +#define EINFO_EINVAL_TICKET \ + __einfo_uniqify ( EINFO_EINVAL, 0x0e, \ + "Invalid New Session Ticket record") #define EIO_ALERT __einfo_error ( EINFO_EIO_ALERT ) #define EINFO_EIO_ALERT \ - __einfo_uniqify ( EINFO_EINVAL, 0x01, \ + __einfo_uniqify ( EINFO_EIO, 0x01, \ "Unknown alert level" ) #define ENOMEM_CONTEXT __einfo_error ( EINFO_ENOMEM_CONTEXT ) #define EINFO_ENOMEM_CONTEXT \ @@ -175,6 +180,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); __einfo_uniqify ( EINFO_EPROTO, 0x01, \ "Illegal protocol version upgrade" ) +/** List of TLS session */ +static LIST_HEAD ( tls_sessions ); + +static void tls_tx_resume_all ( struct tls_session *session ); static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, const void *data, size_t len ); static void tls_clear_cipher ( struct tls_connection *tls, @@ -308,6 +317,28 @@ struct rsa_digestinfo_prefix rsa_md5_sha1_prefix __rsa_digestinfo_prefix = { */ /** + * Free TLS session + * + * @v refcnt Reference counter + */ +static void free_tls_session ( struct refcnt *refcnt ) { + struct tls_session *session = + container_of ( refcnt, struct tls_session, refcnt ); + + /* Sanity check */ + assert ( list_empty ( &session->conn ) ); + + /* Remove from list of sessions */ + list_del ( &session->list ); + + /* Free session ticket */ + free ( session->ticket ); + + /* Free session */ + free ( session ); +} + +/** * Free TLS connection * * @v refcnt Reference counter @@ -315,10 +346,12 @@ struct rsa_digestinfo_prefix rsa_md5_sha1_prefix __rsa_digestinfo_prefix = { static void free_tls ( struct refcnt *refcnt ) { struct tls_connection *tls = container_of ( refcnt, struct tls_connection, refcnt ); + struct tls_session *session = tls->session; struct io_buffer *iobuf; struct io_buffer *tmp; /* Free dynamically-allocated resources */ + free ( tls->new_session_ticket ); tls_clear_cipher ( tls, &tls->tx_cipherspec ); tls_clear_cipher ( tls, &tls->tx_cipherspec_pending ); tls_clear_cipher ( tls, &tls->rx_cipherspec ); @@ -330,8 +363,12 @@ static void free_tls ( struct refcnt *refcnt ) { x509_put ( tls->cert ); x509_chain_put ( tls->chain ); + /* Drop reference to session */ + assert ( list_empty ( &tls->list ) ); + ref_put ( &session->refcnt ); + /* Free TLS structure itself */ - free ( tls ); + free ( tls ); } /** @@ -345,6 +382,7 @@ static void tls_close ( struct tls_connection *tls, int rc ) { /* Remove pending operations, if applicable */ pending_put ( &tls->client_negotiation ); pending_put ( &tls->server_negotiation ); + pending_put ( &tls->validation ); /* Remove process */ process_del ( &tls->process ); @@ -353,6 +391,13 @@ static void tls_close ( struct tls_connection *tls, int rc ) { intf_shutdown ( &tls->cipherstream, rc ); intf_shutdown ( &tls->plainstream, rc ); intf_shutdown ( &tls->validator, rc ); + + /* Remove from session */ + list_del ( &tls->list ); + INIT_LIST_HEAD ( &tls->list ); + + /* Resume all other connections, in case we were the lead connection */ + tls_tx_resume_all ( tls->session ); } /****************************************************************************** @@ -906,6 +951,7 @@ static void tls_restart ( struct tls_connection *tls ) { assert ( ! tls->tx_pending ); assert ( ! is_pending ( &tls->client_negotiation ) ); assert ( ! is_pending ( &tls->server_negotiation ) ); + assert ( ! is_pending ( &tls->validation ) ); /* (Re)initialise handshake context */ digest_init ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx ); @@ -929,6 +975,18 @@ static void tls_tx_resume ( struct tls_connection *tls ) { } /** + * Resume TX state machine for all connections within a session + * + * @v session TLS session + */ +static void tls_tx_resume_all ( struct tls_session *session ) { + struct tls_connection *tls; + + list_for_each_entry ( tls, &session->conn, list ) + tls_tx_resume ( tls ); +} + +/** * Transmit Handshake record * * @v tls TLS connection @@ -953,11 +1011,14 @@ static int tls_send_handshake ( struct tls_connection *tls, * @ret rc Return status code */ static int tls_send_client_hello ( struct tls_connection *tls ) { + struct tls_session *session = tls->session; + size_t name_len = strlen ( session->name ); struct { uint32_t type_length; uint16_t version; uint8_t random[32]; uint8_t session_id_len; + uint8_t session_id[tls->session_id_len]; uint16_t cipher_suite_len; uint16_t cipher_suites[TLS_NUM_CIPHER_SUITES]; uint8_t compression_methods_len; @@ -971,7 +1032,7 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { struct { uint8_t type; uint16_t len; - uint8_t name[ strlen ( tls->name ) ]; + uint8_t name[name_len]; } __attribute__ (( packed )) list[1]; } __attribute__ (( packed )) server_name; uint16_t max_fragment_length_type; @@ -993,18 +1054,27 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { uint8_t data[ tls->secure_renegotiation ? sizeof ( tls->verify.client ) :0]; } __attribute__ (( packed )) renegotiation_info; + uint16_t session_ticket_type; + uint16_t session_ticket_len; + struct { + uint8_t data[session->ticket_len]; + } __attribute__ (( packed )) session_ticket; } __attribute__ (( packed )) extensions; } __attribute__ (( packed )) hello; struct tls_cipher_suite *suite; struct tls_signature_hash_algorithm *sighash; unsigned int i; + /* Construct record */ memset ( &hello, 0, sizeof ( hello ) ); hello.type_length = ( cpu_to_le32 ( TLS_CLIENT_HELLO ) | htonl ( sizeof ( hello ) - sizeof ( hello.type_length ) ) ); hello.version = htons ( tls->version ); memcpy ( &hello.random, &tls->client_random, sizeof ( hello.random ) ); + hello.session_id_len = tls->session_id_len; + memcpy ( hello.session_id, tls->session_id, + sizeof ( hello.session_id ) ); hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) ); i = 0 ; for_each_table_entry ( suite, TLS_CIPHER_SUITES ) hello.cipher_suites[i++] = suite->code; @@ -1018,7 +1088,7 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { hello.extensions.server_name.list[0].type = TLS_SERVER_NAME_HOST_NAME; hello.extensions.server_name.list[0].len = htons ( sizeof ( hello.extensions.server_name.list[0].name )); - memcpy ( hello.extensions.server_name.list[0].name, tls->name, + memcpy ( hello.extensions.server_name.list[0].name, session->name, sizeof ( hello.extensions.server_name.list[0].name ) ); hello.extensions.max_fragment_length_type = htons ( TLS_MAX_FRAGMENT_LENGTH ); @@ -1042,6 +1112,11 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { = sizeof ( hello.extensions.renegotiation_info.data ); memcpy ( hello.extensions.renegotiation_info.data, tls->verify.client, sizeof ( hello.extensions.renegotiation_info.data ) ); + hello.extensions.session_ticket_type = htons ( TLS_SESSION_TICKET ); + hello.extensions.session_ticket_len + = htons ( sizeof ( hello.extensions.session_ticket ) ); + memcpy ( hello.extensions.session_ticket.data, session->ticket, + sizeof ( hello.extensions.session_ticket.data ) ); return tls_send_handshake ( tls, &hello, sizeof ( hello ) ); } @@ -1513,8 +1588,34 @@ static int tls_new_server_hello ( struct tls_connection *tls, if ( ( rc = tls_select_cipher ( tls, hello_b->cipher_suite ) ) != 0 ) return rc; - /* Generate secrets */ - tls_generate_master_secret ( tls ); + /* Reuse or generate master secret */ + if ( hello_a->session_id_len && + ( hello_a->session_id_len == tls->session_id_len ) && + ( memcmp ( session_id, tls->session_id, + tls->session_id_len ) == 0 ) ) { + + /* Session ID match: reuse master secret */ + DBGC ( tls, "TLS %p resuming session ID:\n", tls ); + DBGC_HDA ( tls, 0, tls->session_id, tls->session_id_len ); + + } else { + + /* Generate new master secret */ + tls_generate_master_secret ( tls ); + + /* Record new session ID, if present */ + if ( hello_a->session_id_len && + ( hello_a->session_id_len <= sizeof ( tls->session_id ))){ + tls->session_id_len = hello_a->session_id_len; + memcpy ( tls->session_id, session_id, + tls->session_id_len ); + DBGC ( tls, "TLS %p new session ID:\n", tls ); + DBGC_HDA ( tls, 0, tls->session_id, + tls->session_id_len ); + } + } + + /* Generate keys */ if ( ( rc = tls_generate_keys ( tls ) ) != 0 ) return rc; @@ -1546,6 +1647,57 @@ static int tls_new_server_hello ( struct tls_connection *tls, } /** + * Receive New Session Ticket handshake record + * + * @v tls TLS connection + * @v data Plaintext handshake record + * @v len Length of plaintext handshake record + * @ret rc Return status code + */ +static int tls_new_session_ticket ( struct tls_connection *tls, + const void *data, size_t len ) { + const struct { + uint32_t lifetime; + uint16_t len; + uint8_t ticket[0]; + } __attribute__ (( packed )) *new_session_ticket = data; + size_t ticket_len; + + /* Parse header */ + if ( sizeof ( *new_session_ticket ) > len ) { + DBGC ( tls, "TLS %p received underlength New Session Ticket\n", + tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL_TICKET; + } + ticket_len = ntohs ( new_session_ticket->len ); + if ( ticket_len > ( len - sizeof ( *new_session_ticket ) ) ) { + DBGC ( tls, "TLS %p received overlength New Session Ticket\n", + tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL_TICKET; + } + + /* Free any unapplied new session ticket */ + free ( tls->new_session_ticket ); + tls->new_session_ticket = NULL; + tls->new_session_ticket_len = 0; + + /* Record ticket */ + tls->new_session_ticket = malloc ( ticket_len ); + if ( ! tls->new_session_ticket ) + return -ENOMEM; + memcpy ( tls->new_session_ticket, new_session_ticket->ticket, + ticket_len ); + tls->new_session_ticket_len = ticket_len; + DBGC ( tls, "TLS %p new session ticket:\n", tls ); + DBGC_HDA ( tls, 0, tls->new_session_ticket, + tls->new_session_ticket_len ); + + return 0; +} + +/** * Parse certificate chain * * @v tls TLS connection @@ -1725,6 +1877,7 @@ static int tls_new_server_hello_done ( struct tls_connection *tls, "%s\n", tls, strerror ( rc ) ); return rc; } + pending_get ( &tls->validation ); return 0; } @@ -1739,6 +1892,7 @@ static int tls_new_server_hello_done ( struct tls_connection *tls, */ static int tls_new_finished ( struct tls_connection *tls, const void *data, size_t len ) { + struct tls_session *session = tls->session; struct digest_algorithm *digest = tls->handshake_digest; const struct { uint8_t verify_data[ sizeof ( tls->verify.server ) ]; @@ -1767,6 +1921,39 @@ static int tls_new_finished ( struct tls_connection *tls, /* Mark server as finished */ pending_put ( &tls->server_negotiation ); + /* If we are resuming a session (i.e. if the server Finished + * arrives before the client Finished is sent), then schedule + * transmission of Change Cipher and Finished. + */ + if ( is_pending ( &tls->client_negotiation ) ) { + tls->tx_pending |= ( TLS_TX_CHANGE_CIPHER | TLS_TX_FINISHED ); + tls_tx_resume ( tls ); + } + + /* Record session ID, ticket, and master secret, if applicable */ + if ( tls->session_id_len || tls->new_session_ticket_len ) { + memcpy ( session->master_secret, tls->master_secret, + sizeof ( session->master_secret ) ); + } + if ( tls->session_id_len ) { + session->id_len = tls->session_id_len; + memcpy ( session->id, tls->session_id, sizeof ( session->id ) ); + } + if ( tls->new_session_ticket_len ) { + free ( session->ticket ); + session->ticket = tls->new_session_ticket; + session->ticket_len = tls->new_session_ticket_len; + tls->new_session_ticket = NULL; + tls->new_session_ticket_len = 0; + } + + /* Move to end of session's connection list and allow other + * connections to start making progress. + */ + list_del ( &tls->list ); + list_add_tail ( &tls->list, &session->conn ); + tls_tx_resume_all ( session ); + /* Send notification of a window change */ xfer_window_changed ( &tls->plainstream ); @@ -1822,6 +2009,10 @@ static int tls_new_handshake ( struct tls_connection *tls, case TLS_SERVER_HELLO: rc = tls_new_server_hello ( tls, payload, payload_len ); break; + case TLS_NEW_SESSION_TICKET: + rc = tls_new_session_ticket ( tls, payload, + payload_len ); + break; case TLS_CERTIFICATE: rc = tls_new_certificate ( tls, payload, payload_len ); break; @@ -2383,12 +2574,31 @@ static int tls_plainstream_deliver ( struct tls_connection *tls, return rc; } +/** + * Report job progress + * + * @v tls TLS connection + * @v progress Progress report to fill in + * @ret ongoing_rc Ongoing job status code (if known) + */ +static int tls_progress ( struct tls_connection *tls, + struct job_progress *progress ) { + + /* Return cipherstream or validator progress as applicable */ + if ( is_pending ( &tls->validation ) ) { + return job_progress ( &tls->validator, progress ); + } else { + return job_progress ( &tls->cipherstream, progress ); + } +} + /** TLS plaintext stream interface operations */ static struct interface_operation tls_plainstream_ops[] = { INTF_OP ( xfer_deliver, struct tls_connection *, tls_plainstream_deliver ), INTF_OP ( xfer_window, struct tls_connection *, tls_plainstream_window ), + INTF_OP ( job_progress, struct tls_connection *, tls_progress ), INTF_OP ( intf_close, struct tls_connection *, tls_close ), }; @@ -2608,10 +2818,14 @@ static struct interface_descriptor tls_cipherstream_desc = * @v rc Reason for completion */ static void tls_validator_done ( struct tls_connection *tls, int rc ) { + struct tls_session *session = tls->session; struct tls_cipherspec *cipherspec = &tls->tx_cipherspec_pending; struct pubkey_algorithm *pubkey = cipherspec->suite->pubkey; struct x509_certificate *cert; + /* Mark validation as complete */ + pending_put ( &tls->validation ); + /* Close validator interface */ intf_restart ( &tls->validator, rc ); @@ -2628,9 +2842,9 @@ static void tls_validator_done ( struct tls_connection *tls, int rc ) { assert ( cert != NULL ); /* Verify server name */ - if ( ( rc = x509_check_name ( cert, tls->name ) ) != 0 ) { + if ( ( rc = x509_check_name ( cert, session->name ) ) != 0 ) { DBGC ( tls, "TLS %p server certificate does not match %s: %s\n", - tls, tls->name, strerror ( rc ) ); + tls, session->name, strerror ( rc ) ); goto err; } @@ -2682,6 +2896,8 @@ static struct interface_descriptor tls_validator_desc = * @v tls TLS connection */ static void tls_tx_step ( struct tls_connection *tls ) { + struct tls_session *session = tls->session; + struct tls_connection *conn; int rc; /* Wait for cipherstream to become ready */ @@ -2690,6 +2906,32 @@ static void tls_tx_step ( struct tls_connection *tls ) { /* Send first pending transmission */ if ( tls->tx_pending & TLS_TX_CLIENT_HELLO ) { + /* Serialise server negotiations within a session, to + * provide a consistent view of session IDs and + * session tickets. + */ + list_for_each_entry ( conn, &session->conn, list ) { + if ( conn == tls ) + break; + if ( is_pending ( &conn->server_negotiation ) ) + return; + } + /* Record or generate session ID and associated master secret */ + if ( session->id_len ) { + /* Attempt to resume an existing session */ + memcpy ( tls->session_id, session->id, + sizeof ( tls->session_id ) ); + tls->session_id_len = session->id_len; + memcpy ( tls->master_secret, session->master_secret, + sizeof ( tls->master_secret ) ); + } else { + /* No existing session: use a random session ID */ + assert ( sizeof ( tls->session_id ) == + sizeof ( tls->client_random ) ); + memcpy ( tls->session_id, &tls->client_random, + sizeof ( tls->session_id ) ); + tls->session_id_len = sizeof ( tls->session_id ); + } /* Send Client Hello */ if ( ( rc = tls_send_client_hello ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p could not send Client Hello: %s\n", @@ -2768,6 +3010,60 @@ static struct process_descriptor tls_process_desc = /****************************************************************************** * + * Session management + * + ****************************************************************************** + */ + +/** + * Find or create session for TLS connection + * + * @v tls TLS connection + * @v name Server name + * @ret rc Return status code + */ +static int tls_session ( struct tls_connection *tls, const char *name ) { + struct tls_session *session; + char *name_copy; + int rc; + + /* Find existing matching session, if any */ + list_for_each_entry ( session, &tls_sessions, list ) { + if ( strcmp ( name, session->name ) == 0 ) { + ref_get ( &session->refcnt ); + tls->session = session; + DBGC ( tls, "TLS %p joining session %s\n", tls, name ); + return 0; + } + } + + /* Create new session */ + session = zalloc ( sizeof ( *session ) + strlen ( name ) + + 1 /* NUL */ ); + if ( ! session ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &session->refcnt, free_tls_session ); + name_copy = ( ( ( void * ) session ) + sizeof ( *session ) ); + strcpy ( name_copy, name ); + session->name = name_copy; + INIT_LIST_HEAD ( &session->conn ); + list_add ( &session->list, &tls_sessions ); + + /* Record session */ + tls->session = session; + + DBGC ( tls, "TLS %p created session %s\n", tls, name ); + return 0; + + ref_put ( &session->refcnt ); + err_alloc: + return rc; +} + +/****************************************************************************** + * * Instantiator * ****************************************************************************** @@ -2786,7 +3082,7 @@ int add_tls ( struct interface *xfer, const char *name, } memset ( tls, 0, sizeof ( *tls ) ); ref_init ( &tls->refcnt, free_tls ); - tls->name = name; + INIT_LIST_HEAD ( &tls->list ); intf_init ( &tls->plainstream, &tls_plainstream_desc, &tls->refcnt ); intf_init ( &tls->cipherstream, &tls_cipherstream_desc, &tls->refcnt ); intf_init ( &tls->validator, &tls_validator_desc, &tls->refcnt ); @@ -2809,6 +3105,9 @@ int add_tls ( struct interface *xfer, const char *name, ( sizeof ( tls->pre_master_secret.random ) ) ) ) != 0 ) { goto err_random; } + if ( ( rc = tls_session ( tls, name ) ) != 0 ) + goto err_session; + list_add_tail ( &tls->list, &tls->session->conn ); /* Start negotiation */ tls_restart ( tls ); @@ -2819,6 +3118,7 @@ int add_tls ( struct interface *xfer, const char *name, ref_put ( &tls->refcnt ); return 0; + err_session: err_random: ref_put ( &tls->refcnt ); err_alloc: diff --git a/src/net/validator.c b/src/net/validator.c index 40f778c7d..f6b03ff41 100644 --- a/src/net/validator.c +++ b/src/net/validator.c @@ -40,6 +40,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/base64.h> #include <ipxe/crc32.h> #include <ipxe/ocsp.h> +#include <ipxe/job.h> #include <ipxe/validator.h> #include <config/crypto.h> @@ -49,6 +50,17 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ +struct validator; + +/** A certificate validator action */ +struct validator_action { + /** Name */ + const char *name; + /** Action to take upon completed transfer */ + int ( * done ) ( struct validator *validator, const void *data, + size_t len ); +}; + /** A certificate validator */ struct validator { /** Reference count */ @@ -67,12 +79,31 @@ struct validator { struct ocsp_check *ocsp; /** Data buffer */ struct xfer_buffer buffer; - /** Action to take upon completed transfer */ - int ( * done ) ( struct validator *validator, const void *data, - size_t len ); + + /** Current action */ + const struct validator_action *action; + /** Current certificate + * + * This will always be present within the certificate chain + * and so this pointer does not hold a reference to the + * certificate. + */ + struct x509_certificate *cert; }; /** + * Get validator name (for debug messages) + * + * @v validator Certificate validator + * @ret name Validator name + */ +static const char * validator_name ( struct validator *validator ) { + + /* Use name of first certificate in chain */ + return x509_name ( x509_first ( validator->chain ) ); +} + +/** * Free certificate validator * * @v refcnt Reference count @@ -81,7 +112,8 @@ static void validator_free ( struct refcnt *refcnt ) { struct validator *validator = container_of ( refcnt, struct validator, refcnt ); - DBGC2 ( validator, "VALIDATOR %p freed\n", validator ); + DBGC2 ( validator, "VALIDATOR %p \"%s\" freed\n", + validator, validator_name ( validator ) ); x509_chain_put ( validator->chain ); ocsp_put ( validator->ocsp ); xferbuf_free ( &validator->buffer ); @@ -110,8 +142,29 @@ static void validator_finished ( struct validator *validator, int rc ) { * */ +/** + * Report job progress + * + * @v validator Certificate validator + * @v progress Progress report to fill in + * @ret ongoing_rc Ongoing job status code (if known) + */ +static int validator_progress ( struct validator *validator, + struct job_progress *progress ) { + + /* Report current action, if applicable */ + if ( validator->action ) { + snprintf ( progress->message, sizeof ( progress->message ), + "%s %s", validator->action->name, + x509_name ( validator->cert ) ); + } + + return 0; +} + /** Certificate validator job control interface operations */ static struct interface_operation validator_job_operations[] = { + INTF_OP ( job_progress, struct validator *, validator_progress ), INTF_OP ( intf_close, struct validator *, validator_finished ), }; @@ -165,8 +218,9 @@ static int validator_append ( struct validator *validator, /* Enter certificateSet */ if ( ( rc = asn1_enter ( &cursor, ASN1_SET ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not enter " - "certificateSet: %s\n", validator, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not enter " + "certificateSet: %s\n", validator, + validator_name ( validator ), strerror ( rc ) ); goto err_certificateset; } @@ -176,15 +230,16 @@ static int validator_append ( struct validator *validator, /* Add certificate to chain */ if ( ( rc = x509_append_raw ( certs, cursor.data, cursor.len ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not append " - "certificate: %s\n", - validator, strerror ( rc) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not " + "append certificate: %s\n", validator, + validator_name ( validator ), strerror ( rc) ); DBGC_HDA ( validator, 0, cursor.data, cursor.len ); return rc; } cert = x509_last ( certs ); - DBGC ( validator, "VALIDATOR %p found certificate %s\n", - validator, x509_name ( cert ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" found certificate ", + validator, validator_name ( validator ) ); + DBGC ( validator, "%s\n", x509_name ( cert ) ); /* Move to next certificate */ asn1_skip_any ( &cursor ); @@ -193,15 +248,17 @@ static int validator_append ( struct validator *validator, /* Append certificates to chain */ last = x509_last ( validator->chain ); if ( ( rc = x509_auto_append ( validator->chain, certs ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not append " - "certificates: %s\n", validator, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not append " + "certificates: %s\n", validator, + validator_name ( validator ), strerror ( rc ) ); goto err_auto_append; } /* Check that at least one certificate has been added */ if ( last == x509_last ( validator->chain ) ) { - DBGC ( validator, "VALIDATOR %p failed to append any " - "applicable certificates\n", validator ); + DBGC ( validator, "VALIDATOR %p \"%s\" failed to append any " + "applicable certificates\n", validator, + validator_name ( validator ) ); rc = -EACCES; goto err_no_progress; } @@ -219,15 +276,22 @@ static int validator_append ( struct validator *validator, return rc; } +/** Cross-signing certificate download validator action */ +static const struct validator_action validator_crosscert = { + .name = "XCRT", + .done = validator_append, +}; + /** * Start download of cross-signing certificate * * @v validator Certificate validator - * @v issuer Required issuer + * @v cert X.509 certificate * @ret rc Return status code */ static int validator_start_download ( struct validator *validator, - const struct asn1_cursor *issuer ) { + struct x509_certificate *cert ) { + const struct asn1_cursor *issuer = &cert->issuer.raw; const char *crosscert; char *crosscert_copy; char *uri_string; @@ -261,17 +325,21 @@ static int validator_start_download ( struct validator *validator, crosscert, crc ); base64_encode ( issuer->data, issuer->len, ( uri_string + len ), ( uri_string_len - len ) ); - DBGC ( validator, "VALIDATOR %p downloading cross-signed certificate " - "from %s\n", validator, uri_string ); + DBGC ( validator, "VALIDATOR %p \"%s\" downloading ", + validator, validator_name ( validator ) ); + DBGC ( validator, "\"%s\" cross-signature from %s\n", + x509_name ( cert ), uri_string ); /* Set completion handler */ - validator->done = validator_append; + validator->action = &validator_crosscert; + validator->cert = cert; /* Open URI */ if ( ( rc = xfer_open_uri_string ( &validator->xfer, uri_string ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not open %s: %s\n", - validator, uri_string, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not open %s: " + "%s\n", validator, validator_name ( validator ), + uri_string, strerror ( rc ) ); goto err_open_uri_string; } @@ -307,16 +375,18 @@ static int validator_ocsp_validate ( struct validator *validator, /* Record OCSP response */ if ( ( rc = ocsp_response ( validator->ocsp, data, len ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not record OCSP " - "response: %s\n", validator, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not record OCSP " + "response: %s\n", validator, + validator_name ( validator ),strerror ( rc ) ); return rc; } /* Validate OCSP response */ now = time ( NULL ); if ( ( rc = ocsp_validate ( validator->ocsp, now ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not validate OCSP " - "response: %s\n", validator, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not validate " + "OCSP response: %s\n", validator, + validator_name ( validator ), strerror ( rc ) ); return rc; } @@ -327,6 +397,12 @@ static int validator_ocsp_validate ( struct validator *validator, return 0; } +/** OCSP validator action */ +static const struct validator_action validator_ocsp = { + .name = "OCSP", + .done = validator_ocsp_validate, +}; + /** * Start OCSP check * @@ -344,22 +420,27 @@ static int validator_start_ocsp ( struct validator *validator, /* Create OCSP check */ assert ( validator->ocsp == NULL ); if ( ( rc = ocsp_check ( cert, issuer, &validator->ocsp ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not create OCSP check: " - "%s\n", validator, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not create OCSP " + "check: %s\n", validator, validator_name ( validator ), + strerror ( rc ) ); return rc; } /* Set completion handler */ - validator->done = validator_ocsp_validate; + validator->action = &validator_ocsp; + validator->cert = cert; /* Open URI */ uri_string = validator->ocsp->uri_string; - DBGC ( validator, "VALIDATOR %p performing OCSP check at %s\n", - validator, uri_string ); + DBGC ( validator, "VALIDATOR %p \"%s\" checking ", + validator, validator_name ( validator ) ); + DBGC ( validator, "\"%s\" via %s\n", + x509_name ( cert ), uri_string ); if ( ( rc = xfer_open_uri_string ( &validator->xfer, uri_string ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not open %s: %s\n", - validator, uri_string, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not open %s: " + "%s\n", validator, validator_name ( validator ), + uri_string, strerror ( rc ) ); return rc; } @@ -385,16 +466,18 @@ static void validator_xfer_close ( struct validator *validator, int rc ) { /* Check for errors */ if ( rc != 0 ) { - DBGC ( validator, "VALIDATOR %p transfer failed: %s\n", - validator, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" transfer failed: %s\n", + validator, validator_name ( validator ), + strerror ( rc ) ); goto err_transfer; } - DBGC2 ( validator, "VALIDATOR %p transfer complete\n", validator ); + DBGC2 ( validator, "VALIDATOR %p \"%s\" transfer complete\n", + validator, validator_name ( validator ) ); /* Process completed download */ - assert ( validator->done != NULL ); - if ( ( rc = validator->done ( validator, validator->buffer.data, - validator->buffer.len ) ) != 0 ) + assert ( validator->action != NULL ); + if ( ( rc = validator->action->done ( validator, validator->buffer.data, + validator->buffer.len ) ) != 0 ) goto err_append; /* Free downloaded data */ @@ -426,8 +509,9 @@ static int validator_xfer_deliver ( struct validator *validator, /* Add data to buffer */ if ( ( rc = xferbuf_deliver ( &validator->buffer, iob_disown ( iobuf ), meta ) ) != 0 ) { - DBGC ( validator, "VALIDATOR %p could not receive data: %s\n", - validator, strerror ( rc ) ); + DBGC ( validator, "VALIDATOR %p \"%s\" could not receive " + "data: %s\n", validator, validator_name ( validator ), + strerror ( rc ) ); validator_finished ( validator, rc ); return rc; } @@ -471,6 +555,8 @@ static void validator_step ( struct validator *validator ) { now = time ( NULL ); if ( ( rc = x509_validate_chain ( validator->chain, now, NULL, NULL ) ) == 0 ) { + DBGC ( validator, "VALIDATOR %p \"%s\" validated\n", + validator, validator_name ( validator ) ); validator_finished ( validator, 0 ); return; } @@ -514,8 +600,7 @@ static void validator_step ( struct validator *validator ) { /* Otherwise, try to download a suitable cross-signing * certificate. */ - if ( ( rc = validator_start_download ( validator, - &last->issuer.raw ) ) != 0 ) { + if ( ( rc = validator_start_download ( validator, last ) ) != 0 ) { validator_finished ( validator, rc ); return; } @@ -567,8 +652,8 @@ int create_validator ( struct interface *job, struct x509_chain *chain ) { /* Attach parent interface, mortalise self, and return */ intf_plug_plug ( &validator->job, job ); ref_put ( &validator->refcnt ); - DBGC2 ( validator, "VALIDATOR %p validating X509 chain %p\n", - validator, validator->chain ); + DBGC2 ( validator, "VALIDATOR %p \"%s\" validating X509 chain %p\n", + validator, validator_name ( validator ), validator->chain ); return 0; validator_finished ( validator, rc ); diff --git a/src/net/vlan.c b/src/net/vlan.c index f515c2dc9..90f2934de 100644 --- a/src/net/vlan.c +++ b/src/net/vlan.c @@ -199,7 +199,8 @@ static void vlan_sync ( struct net_device *netdev ) { * @v tag VLAN tag * @ret netdev VLAN device, if any */ -struct net_device * vlan_find ( struct net_device *trunk, unsigned int tag ) { +static struct net_device * vlan_find ( struct net_device *trunk, + unsigned int tag ) { struct net_device *netdev; struct vlan_device *vlan; @@ -506,3 +507,47 @@ struct net_driver vlan_driver __net_driver = { .notify = vlan_notify, .remove = vlan_remove, }; + +/** + * Add VLAN tag-stripped packet to receive queue + * + * @v netdev Network device + * @v tag VLAN tag, or zero + * @v iobuf I/O buffer + */ +void vlan_netdev_rx ( struct net_device *netdev, unsigned int tag, + struct io_buffer *iobuf ) { + struct net_device *vlan; + + /* Identify VLAN device, if applicable */ + if ( tag ) { + if ( ( vlan = vlan_find ( netdev, tag ) ) == NULL ) { + netdev_rx_err ( netdev, iobuf, -ENODEV ); + return; + } + netdev = vlan; + } + + /* Hand off to network device */ + netdev_rx ( netdev, iobuf ); +} + +/** + * Discard received VLAN tag-stripped packet + * + * @v netdev Network device + * @v tag VLAN tag, or zero + * @v iobuf I/O buffer, or NULL + * @v rc Packet status code + */ +void vlan_netdev_rx_err ( struct net_device *netdev, unsigned int tag, + struct io_buffer *iobuf, int rc ) { + struct net_device *vlan; + + /* Identify VLAN device, if applicable */ + if ( tag && ( ( vlan = vlan_find ( netdev, tag ) ) != NULL ) ) + netdev = vlan; + + /* Hand off to network device */ + netdev_rx_err ( netdev, iobuf, rc ); +} diff --git a/src/util/elf2efi.c b/src/util/elf2efi.c index 2c5b9df8a..bcd53c9af 100644 --- a/src/util/elf2efi.c +++ b/src/util/elf2efi.c @@ -458,6 +458,7 @@ static struct pe_section * process_section ( struct elf_file *elf, struct pe_header *pe_header ) { struct pe_section *new; const char *name; + size_t name_len; size_t section_memsz; size_t section_filesz; unsigned long code_start; @@ -494,7 +495,10 @@ static struct pe_section * process_section ( struct elf_file *elf, memset ( new, 0, sizeof ( *new ) + section_filesz ); /* Fill in section header details */ - strncpy ( ( char * ) new->hdr.Name, name, sizeof ( new->hdr.Name ) ); + name_len = strlen ( name ); + if ( name_len > sizeof ( new->hdr.Name ) ) + name_len = sizeof ( new->hdr.Name ); + memcpy ( new->hdr.Name, name, name_len ); new->hdr.Misc.VirtualSize = section_memsz; new->hdr.VirtualAddress = shdr->sh_addr; new->hdr.SizeOfRawData = section_filesz; |
