From 64b4452bca04af433f1c98ab782c0e93cd5c88c0 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 19 Feb 2019 18:47:12 +0000 Subject: [efi] Blacklist the Dell Ip4ConfigDxe driver On a Dell OptiPlex 7010, calling DisconnectController() on the LOM device handle will lock up the system. Debugging shows that execution is trapped in an infinite loop that is somehow trying to reconnect drivers (without going via ConnectController()). The problem can be reproduced in the UEFI shell with no iPXE code present, by using the "disconnect" command. Experimentation shows that the only fix is to unload (rather than just disconnect) the "Ip4ConfigDxe" driver. Add the concept of a blacklist of UEFI drivers that will be automatically unloaded when iPXE runs as an application, and add the Dell Ip4ConfigDxe driver to this blacklist. Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_blacklist.h | 13 +++++++++++++ src/include/ipxe/errfile.h | 1 + 2 files changed, 14 insertions(+) create mode 100644 src/include/ipxe/efi/efi_blacklist.h (limited to 'src/include') 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..ce67fc66d 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -375,6 +375,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 ) /** @} */ -- cgit v1.2.3-55-g7522 From 272fe32529103dd39875a9fbed5cfdf1a059e294 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 21 Feb 2019 11:32:25 +0000 Subject: [tls] Support stateful session resumption Record the session ID (if any) provided by the server and attempt to reuse it for any concurrent connections to the same server. If multiple connections are initiated concurrently (e.g. when using PeerDist) then defer sending the ClientHello for all but the first connection, to allow time for the first connection to potentially obtain a session ID (and thereby speed up the negotiation for all remaining connections). Signed-off-by: Michael Brown --- src/include/ipxe/tls.h | 31 +++++++- src/net/tls.c | 199 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 220 insertions(+), 10 deletions(-) (limited to 'src/include') diff --git a/src/include/ipxe/tls.h b/src/include/ipxe/tls.h index b1e702e18..0375a722a 100644 --- a/src/include/ipxe/tls.h +++ b/src/include/ipxe/tls.h @@ -242,13 +242,40 @@ 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; + /** 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; + /** Plaintext stream */ struct interface plainstream; /** Ciphertext stream */ diff --git a/src/net/tls.c b/src/net/tls.c index 9d994cd77..a2f1f6528 100644 --- a/src/net/tls.c +++ b/src/net/tls.c @@ -175,6 +175,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, @@ -307,6 +311,25 @@ 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 */ + free ( session ); +} + /** * Free TLS connection * @@ -315,6 +338,7 @@ 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; @@ -330,8 +354,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 ); } /** @@ -353,6 +381,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 ); } /****************************************************************************** @@ -928,6 +963,18 @@ static void tls_tx_resume ( struct tls_connection *tls ) { process_add ( &tls->process ); } +/** + * 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 * @@ -953,11 +1000,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[session->id_len]; uint16_t cipher_suite_len; uint16_t cipher_suites[TLS_NUM_CIPHER_SUITES]; uint8_t compression_methods_len; @@ -971,7 +1021,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; @@ -999,12 +1049,22 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { struct tls_signature_hash_algorithm *sighash; unsigned int i; + /* Record requested session ID and associated master secret */ + 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 ) ); + + /* 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 +1078,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 ); @@ -1513,8 +1573,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; @@ -1739,6 +1825,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 +1854,30 @@ 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 and master secret, if applicable */ + if ( tls->session_id_len ) { + session->id_len = tls->session_id_len; + memcpy ( session->id, tls->session_id, sizeof ( session->id ) ); + memcpy ( session->master_secret, tls->master_secret, + sizeof ( session->master_secret ) ); + } + + /* 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 ); @@ -2608,6 +2719,7 @@ 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; @@ -2628,9 +2740,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 +2794,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 +2804,17 @@ static void tls_tx_step ( struct tls_connection *tls ) { /* Send first pending transmission */ if ( tls->tx_pending & TLS_TX_CLIENT_HELLO ) { + /* Wait for session ID to become available unless we + * are the lead connection within the session. + */ + if ( session->id_len == 0 ) { + list_for_each_entry ( conn, &session->conn, list ) { + if ( conn == tls ) + break; + if ( is_pending ( &conn->server_negotiation ) ) + return; + } + } /* Send Client Hello */ if ( ( rc = tls_send_client_hello ( tls ) ) != 0 ) { DBGC ( tls, "TLS %p could not send Client Hello: %s\n", @@ -2766,6 +2891,60 @@ static void tls_tx_step ( struct tls_connection *tls ) { static struct process_descriptor tls_process_desc = PROC_DESC_ONCE ( struct tls_connection, process, tls_tx_step ); +/****************************************************************************** + * + * 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 +2965,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 +2988,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 +3001,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: -- cgit v1.2.3-55-g7522 From eaba1a22b8552f0410fe1519d7d0b606dc9ef3bb Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 6 Mar 2019 15:02:02 +0000 Subject: [tls] Support stateless session resumption Add support for RFC5077 session ticket extensions to allow for stateless TLS session resumption. Signed-off-by: Michael Brown --- src/include/ipxe/tls.h | 12 +++++ src/net/tls.c | 129 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 122 insertions(+), 19 deletions(-) (limited to 'src/include') diff --git a/src/include/ipxe/tls.h b/src/include/ipxe/tls.h index 0375a722a..4bffde7c2 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 @@ -255,6 +259,10 @@ struct tls_session { 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]; @@ -275,6 +283,10 @@ struct tls_connection { 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; diff --git a/src/net/tls.c b/src/net/tls.c index 43fe4b4d2..1cd37e776 100644 --- a/src/net/tls.c +++ b/src/net/tls.c @@ -102,6 +102,10 @@ 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_EIO, 0x01, \ @@ -326,6 +330,9 @@ static void free_tls_session ( struct refcnt *refcnt ) { /* Remove from list of sessions */ list_del ( &session->list ); + /* Free session ticket */ + free ( session->ticket ); + /* Free session */ free ( session ); } @@ -343,6 +350,7 @@ static void free_tls ( struct refcnt *refcnt ) { 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 ); @@ -1007,7 +1015,7 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { uint16_t version; uint8_t random[32]; uint8_t session_id_len; - uint8_t session_id[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; @@ -1043,18 +1051,17 @@ 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; - /* Record requested session ID and associated master secret */ - 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 ) ); - /* Construct record */ memset ( &hello, 0, sizeof ( hello ) ); hello.type_length = ( cpu_to_le32 ( TLS_CLIENT_HELLO ) | @@ -1102,6 +1109,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 ) ); } @@ -1631,6 +1643,57 @@ static int tls_new_server_hello ( struct tls_connection *tls, return 0; } +/** + * 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 * @@ -1863,12 +1926,21 @@ static int tls_new_finished ( struct tls_connection *tls, tls_tx_resume ( tls ); } - /* Record session ID and master secret, if applicable */ + /* 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 ) ); - memcpy ( session->master_secret, tls->master_secret, - sizeof ( session->master_secret ) ); + } + 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 @@ -1933,6 +2005,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; @@ -2804,16 +2880,31 @@ static void tls_tx_step ( struct tls_connection *tls ) { /* Send first pending transmission */ if ( tls->tx_pending & TLS_TX_CLIENT_HELLO ) { - /* Wait for session ID to become available unless we - * are the lead connection within the session. + /* Serialise server negotiations within a session, to + * provide a consistent view of session IDs and + * session tickets. */ - if ( session->id_len == 0 ) { - list_for_each_entry ( conn, &session->conn, list ) { - if ( conn == tls ) - break; - if ( is_pending ( &conn->server_negotiation ) ) - return; - } + 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 ) { -- cgit v1.2.3-55-g7522 From 7b63c1275f33e0fa20c0e59dcc1756899533823c Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 10 Mar 2019 17:27:33 +0000 Subject: [tls] Display validator messages only while validation is in progress Allow the cipherstream to report progress status messages during connection establishment. Signed-off-by: Michael Brown --- src/include/ipxe/tls.h | 2 ++ src/net/tls.c | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/include') diff --git a/src/include/ipxe/tls.h b/src/include/ipxe/tls.h index 4bffde7c2..febbdc589 100644 --- a/src/include/ipxe/tls.h +++ b/src/include/ipxe/tls.h @@ -335,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/net/tls.c b/src/net/tls.c index 510bef8c4..746274d61 100644 --- a/src/net/tls.c +++ b/src/net/tls.c @@ -382,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 ); @@ -950,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 ); @@ -1875,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; } @@ -2582,10 +2585,10 @@ static int tls_progress ( struct tls_connection *tls, struct job_progress *progress ) { /* Return cipherstream or validator progress as applicable */ - if ( tls_ready ( tls ) ) { - return job_progress ( &tls->cipherstream, progress ); - } else { + if ( is_pending ( &tls->validation ) ) { return job_progress ( &tls->validator, progress ); + } else { + return job_progress ( &tls->cipherstream, progress ); } } @@ -2820,6 +2823,9 @@ static void tls_validator_done ( struct tls_connection *tls, int rc ) { 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 ); -- cgit v1.2.3-55-g7522 From b6ffe28a21c53a0946d95751c905d9e0b6c3b630 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 10 Mar 2019 17:58:56 +0000 Subject: [ocsp] Accept response certID with missing hashAlgorithm parameters One of the design goals of ASN.1 DER is to provide a canonical serialization of a data structure, thereby allowing for equality of values to be tested by simply comparing the serialized bytes. Some OCSP servers will modify the request certID to omit the optional (and null) "parameters" portion of the hashAlgorithm. This is arguably legal but breaks the ability to perform a straightforward bitwise comparison on the entire certID field between request and response. Fix by comparing the OID-identified hashAlgorithm separately from the remaining certID fields. Originally-fixed-by: Thilo Fromm Signed-off-by: Michael Brown --- src/crypto/ocsp.c | 42 ++++++++++++++++++++++++++++++------------ src/include/ipxe/ocsp.h | 4 ++-- 2 files changed, 32 insertions(+), 14 deletions(-) (limited to 'src/include') 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/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 */ -- cgit v1.2.3-55-g7522 From afee77d816f42c7e405c065395c6a7f4dc2aade1 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 22 Apr 2019 14:43:23 +0100 Subject: [pci] Add support for PCI MSI-X interrupts The Intel 40 Gigabit Ethernet virtual functions support only MSI-X interrupts, and will write back completed interrupt descriptors only when the device attempts to raise an interrupt (or when a complete cacheline of receive descriptors has been completed). We cannot actually use MSI-X interrupts within iPXE, since we never have ownership of the APIC. However, an MSI-X interrupt is fundamentally just a DMA write of a single dword to an arbitrary address. We can therefore configure the device to "raise" an interrupt by writing a meaningless value to an otherwise unused memory location: this is sufficient to trigger the receive descriptor writeback logic. Signed-off-by: Michael Brown --- src/drivers/bus/pcimsix.c | 251 +++++++++++++++++++++++++++++++++++++++++++++ src/include/ipxe/errfile.h | 1 + src/include/ipxe/pci.h | 11 ++ src/include/ipxe/pcimsix.h | 77 ++++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 src/drivers/bus/pcimsix.c create mode 100644 src/include/ipxe/pcimsix.h (limited to 'src/include') 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 . + * + * 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 +#include +#include +#include +#include + +/** @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 ""; + } +} + +/** + * 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/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index ce67fc66d..02e13d11b 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -205,6 +205,7 @@ 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_aoe ( ERRFILE_NET | 0x00000000 ) #define ERRFILE_arp ( ERRFILE_NET | 0x00010000 ) 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 + +/** 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 */ -- cgit v1.2.3-55-g7522 From fe680c8228563369804948010954128aacb7db74 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sat, 27 Apr 2019 20:12:01 +0100 Subject: [vlan] Provide vlan_netdev_rx() and vlan_netdev_rx_err() The Hermon driver uses vlan_find() to identify the appropriate VLAN device for packets that are received with the VLAN tag already stripped out by the hardware. Generalise this capability and expose it for use by other network card drivers. Signed-off-by: Michael Brown --- src/drivers/infiniband/hermon.c | 18 ++++++---------- src/include/ipxe/vlan.h | 8 +++++-- src/net/netdevice.c | 34 +++++++++++++++++++++++------ src/net/vlan.c | 47 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 22 deletions(-) (limited to 'src/include') 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/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 + /** 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/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/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 ); +} -- cgit v1.2.3-55-g7522 From a95966955c6904b975b0ccec0dd338385fb3066b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 24 Apr 2019 17:15:49 +0100 Subject: [intelxl] Add driver for Intel 40 Gigabit Ethernet NIC virtual functions Signed-off-by: Michael Brown --- src/drivers/net/intelxl.h | 185 +++++++++++- src/drivers/net/intelxlvf.c | 719 ++++++++++++++++++++++++++++++++++++++++++++ src/drivers/net/intelxlvf.h | 86 ++++++ src/include/ipxe/errfile.h | 1 + 4 files changed, 989 insertions(+), 2 deletions(-) create mode 100644 src/drivers/net/intelxlvf.c create mode 100644 src/drivers/net/intelxlvf.h (limited to 'src/include') diff --git a/src/drivers/net/intelxl.h b/src/drivers/net/intelxl.h index 414477434..80586cef0 100644 --- a/src/drivers/net/intelxl.h +++ b/src/drivers/net/intelxl.h @@ -20,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 /****************************************************************************** * @@ -310,6 +310,166 @@ struct intelxl_admin_link_params { /** 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 */ @@ -342,6 +502,18 @@ 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 )); @@ -867,12 +1039,21 @@ struct intelxl_nic { 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 */ 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#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/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 02e13d11b..1a92b6ce2 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -206,6 +206,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #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 ) -- cgit v1.2.3-55-g7522 From 3fb3ffccea717ce10bb29dfd8a9e761c7387626e Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 14 Jul 2019 14:05:48 +0100 Subject: [build] Fix use of inline assembly on GCC 8 ARM64 builds Commit 1a7746603 ("[build] Fix use of inline assembly on GCC 4.8 ARM64 builds") switched from using "%c0" to "%a0" in order to avoid an "invalid operand prefix" error on the ARM64 version of GCC 4.8. It appears that the ARM64 version of GCC 8 now produces an "invalid address mode" error for the "%a0" form, but is happy with the original "%c0" form. Switch back to using the "%c0" form, on the assumption that the requirement for "%a0" was a temporary aberration. Originally-fixed-by: John L. Jolly Signed-off-by: Michael Brown --- src/include/errno.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/include') 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" \ -- cgit v1.2.3-55-g7522 From e520a51df1c65d750798420d5ba1bab4271db264 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 19 Jul 2019 17:35:39 +0100 Subject: [fdt] Add ability to parse a MAC address from a flattened device tree The Raspberry Pi NIC has no EEPROM to hold the MAC address. The platform firmware (e.g. UEFI or U-Boot) will typically obtain the MAC address from the VideoCore firmware and add it to the device tree, which is then made available to subsequent programs such as iPXE or the Linux kernel. Add the ability to parse a flattened device tree and to extract the MAC address. Signed-off-by: Michael Brown --- src/config/config_fdt.c | 38 ++++ src/config/fdt.h | 16 ++ src/core/fdt.c | 486 +++++++++++++++++++++++++++++++++++++++++++++ src/include/ipxe/errfile.h | 1 + src/include/ipxe/fdt.h | 102 ++++++++++ 5 files changed, 643 insertions(+) create mode 100644 src/config/config_fdt.c create mode 100644 src/config/fdt.h create mode 100644 src/core/fdt.c create mode 100644 src/include/ipxe/fdt.h (limited to 'src/include') diff --git a/src/config/config_fdt.c b/src/config/config_fdt.c new file mode 100644 index 000000000..85d62ace1 --- /dev/null +++ b/src/config/config_fdt.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 Michael Brown . + * + * 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 + +/** @file + * + * Flattened Device Tree configuration options + * + */ + +PROVIDE_REQUIRING_SYMBOL(); + +/* + * Drag in devicetree sources + */ 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 + +#include + +#endif /* CONFIG_FDT_H */ 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 . + * + * 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 +#include +#include +#include +#include +#include + +/** @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/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 1a92b6ce2..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 ) 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 + +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 */ -- cgit v1.2.3-55-g7522