From 17d28f48776b909d031bcb0435c852ade1bd8988 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 11 Jan 2011 00:53:50 +0000 Subject: [PATCH] [nvo] Allow resizing of non-volatile stored option blocks Signed-off-by: Michael Brown --- src/core/nvo.c | 152 +++++++++++++++++++++++----------- src/drivers/net/etherfabric.c | 2 +- src/drivers/net/myri10ge.c | 1 + src/drivers/net/natsemi.c | 2 +- src/drivers/net/rtl8139.c | 2 +- src/include/ipxe/dhcpopts.h | 1 + src/include/ipxe/nvo.h | 12 ++- src/net/dhcpopts.c | 2 +- 8 files changed, 120 insertions(+), 54 deletions(-) diff --git a/src/core/nvo.c b/src/core/nvo.c index c5968c6b..f4da407a 100644 --- a/src/core/nvo.c +++ b/src/core/nvo.c @@ -49,6 +49,73 @@ static unsigned int nvo_checksum ( struct nvo_block *nvo ) { return sum; } +/** + * Reallocate non-volatile stored options block + * + * @v nvo Non-volatile options block + * @v len New length + * @ret rc Return status code + */ +static int nvo_realloc ( struct nvo_block *nvo, size_t len ) { + void *new_data; + + /* Reallocate data */ + new_data = realloc ( nvo->data, len ); + if ( ! new_data ) { + DBGC ( nvo, "NVO %p could not allocate %zd bytes\n", + nvo, len ); + return -ENOMEM; + } + nvo->data = new_data; + nvo->len = len; + + /* Update DHCP option block */ + if ( len ) { + nvo->dhcpopts.data = ( nvo->data + 1 /* checksum */ ); + nvo->dhcpopts.alloc_len = ( len - 1 /* checksum */ ); + } else { + nvo->dhcpopts.data = NULL; + nvo->dhcpopts.used_len = 0; + nvo->dhcpopts.alloc_len = 0; + } + + return 0; +} + +/** + * Reallocate non-volatile stored options DHCP option block + * + * @v options DHCP option block + * @v len New length + * @ret rc Return status code + */ +static int nvo_realloc_dhcpopt ( struct dhcp_options *options, size_t len ) { + struct nvo_block *nvo = + container_of ( options, struct nvo_block, dhcpopts ); + int rc; + + /* Refuse to reallocate if we have no way to resize the block */ + if ( ! nvo->resize ) + return dhcpopt_no_realloc ( options, len ); + + /* Allow one byte for the checksum (if any data is present) */ + if ( len ) + len += 1; + + /* Resize underlying non-volatile options block */ + if ( ( rc = nvo->resize ( nvo, len ) ) != 0 ) { + DBGC ( nvo, "NVO %p could not resize to %zd bytes: %s\n", + nvo, len, strerror ( rc ) ); + return rc; + } + + /* Reallocate in-memory options block */ + if ( ( rc = nvo_realloc ( nvo, len ) ) != 0 ) + return rc; + + return 0; +} + /** * Load non-volatile stored options from non-volatile storage device * @@ -56,8 +123,15 @@ static unsigned int nvo_checksum ( struct nvo_block *nvo ) { * @ret rc Return status code */ static int nvo_load ( struct nvo_block *nvo ) { + uint8_t *options_data = nvo->dhcpopts.data; int rc; + /* Skip reading zero-length NVO fields */ + if ( nvo->len == 0 ) { + DBGC ( nvo, "NVO %p is empty; skipping load\n", nvo ); + return 0; + } + /* Read data */ if ( ( rc = nvs_read ( nvo->nvs, nvo->address, nvo->data, nvo->len ) ) != 0 ) { @@ -66,6 +140,20 @@ static int nvo_load ( struct nvo_block *nvo ) { return rc; } + /* If checksum fails, or options data starts with a zero, + * assume the whole block is invalid. This should capture the + * case of random initial contents. + */ + if ( ( nvo_checksum ( nvo ) != 0 ) || ( options_data[0] == 0 ) ) { + DBGC ( nvo, "NVO %p has checksum %02x and initial byte %02x; " + "assuming empty\n", nvo, nvo_checksum ( nvo ), + options_data[0] ); + memset ( nvo->data, 0, nvo->len ); + } + + /* Rescan DHCP option block */ + dhcpopt_update_used_len ( &nvo->dhcpopts ); + DBGC ( nvo, "NVO %p loaded from non-volatile storage\n", nvo ); return 0; } @@ -80,8 +168,9 @@ static int nvo_save ( struct nvo_block *nvo ) { uint8_t *checksum = nvo->data; int rc; - /* Recalculate checksum */ - *checksum -= nvo_checksum ( nvo ); + /* Recalculate checksum, if applicable */ + if ( nvo->len > 0 ) + *checksum -= nvo_checksum ( nvo ); /* Write data */ if ( ( rc = nvs_write ( nvo->nvs, nvo->address, nvo->data, @@ -95,38 +184,6 @@ static int nvo_save ( struct nvo_block *nvo ) { return 0; } -/** - * Parse stored options - * - * @v nvo Non-volatile options block - * - * Verifies that the options data is valid, and configures the DHCP - * options block. If the data is not valid, it is replaced with an - * empty options block. - */ -static void nvo_init_dhcpopts ( struct nvo_block *nvo ) { - uint8_t *options_data; - size_t options_len; - - /* Steal one byte for the checksum */ - options_data = ( nvo->data + 1 ); - options_len = ( nvo->len - 1 ); - - /* If checksum fails, or options data starts with a zero, - * assume the whole block is invalid. This should capture the - * case of random initial contents. - */ - if ( ( nvo_checksum ( nvo ) != 0 ) || ( options_data[0] == 0 ) ) { - DBGC ( nvo, "NVO %p has checksum %02x and initial byte %02x; " - "assuming empty\n", nvo, nvo_checksum ( nvo ), - options_data[0] ); - memset ( nvo->data, 0, nvo->len ); - } - - dhcpopt_init ( &nvo->dhcpopts, options_data, options_len, - dhcpopt_no_realloc ); -} - /** * Store value of NVO setting * @@ -190,13 +247,18 @@ static struct settings_operations nvo_settings_operations = { * @v nvs Underlying non-volatile storage device * @v address Address within NVS device * @v len Length of non-volatile options data + * @v resize Resize method * @v refcnt Containing object reference counter, or NULL */ void nvo_init ( struct nvo_block *nvo, struct nvs_device *nvs, - size_t address, size_t len, struct refcnt *refcnt ) { + size_t address, size_t len, + int ( * resize ) ( struct nvo_block *nvo, size_t len ), + struct refcnt *refcnt ) { nvo->nvs = nvs; nvo->address = address; nvo->len = len; + nvo->resize = resize; + dhcpopt_init ( &nvo->dhcpopts, NULL, 0, nvo_realloc_dhcpopt ); settings_init ( &nvo->settings, &nvo_settings_operations, refcnt, 0 ); } @@ -211,20 +273,14 @@ int register_nvo ( struct nvo_block *nvo, struct settings *parent ) { int rc; /* Allocate memory for options */ - nvo->data = zalloc ( nvo->len ); - if ( ! nvo->data ) { - DBGC ( nvo, "NVO %p could not allocate %zd bytes\n", - nvo, nvo->len ); - rc = -ENOMEM; - goto err_malloc; - } + if ( ( rc = nvo_realloc ( nvo, nvo->len ) ) != 0 ) + goto err_realloc; /* Read data from NVS */ if ( ( rc = nvo_load ( nvo ) ) != 0 ) goto err_load; - /* Verify and register options */ - nvo_init_dhcpopts ( nvo ); + /* Register settings */ if ( ( rc = register_settings ( &nvo->settings, parent, "nvo" ) ) != 0 ) goto err_register; @@ -233,9 +289,8 @@ int register_nvo ( struct nvo_block *nvo, struct settings *parent ) { err_register: err_load: - free ( nvo->data ); - nvo->data = NULL; - err_malloc: + nvo_realloc ( nvo, 0 ); + err_realloc: return rc; } @@ -246,7 +301,6 @@ int register_nvo ( struct nvo_block *nvo, struct settings *parent ) { */ void unregister_nvo ( struct nvo_block *nvo ) { unregister_settings ( &nvo->settings ); - free ( nvo->data ); - nvo->data = NULL; + nvo_realloc ( nvo, 0 ); DBGC ( nvo, "NVO %p unregistered\n", nvo ); } diff --git a/src/drivers/net/etherfabric.c b/src/drivers/net/etherfabric.c index 77d21247..836b85a9 100644 --- a/src/drivers/net/etherfabric.c +++ b/src/drivers/net/etherfabric.c @@ -3273,7 +3273,7 @@ falcon_probe_spi ( struct efab_nic *efab ) /* If the device has EEPROM attached, then advertise NVO space */ if ( has_eeprom ) { nvo_init ( &efab->nvo, &efab->spi_eeprom.nvs, 0x100, 0xf0, - &efab->netdev->refcnt ); + NULL, &efab->netdev->refcnt ); } return 0; diff --git a/src/drivers/net/myri10ge.c b/src/drivers/net/myri10ge.c index bc730ea9..c7b9dfa2 100644 --- a/src/drivers/net/myri10ge.c +++ b/src/drivers/net/myri10ge.c @@ -732,6 +732,7 @@ static int myri10ge_nv_init ( struct myri10ge_private *priv ) nvo_init ( &priv->nvo, &priv->nvs, nvo_fragment_pos, 0x200, + NULL, & myri10ge_netdev (priv) -> refcnt ); rc = register_nvo ( &priv->nvo, netdev_settings ( myri10ge_netdev ( priv ) ) ); diff --git a/src/drivers/net/natsemi.c b/src/drivers/net/natsemi.c index 61073b59..da2f0886 100644 --- a/src/drivers/net/natsemi.c +++ b/src/drivers/net/natsemi.c @@ -154,7 +154,7 @@ static void natsemi_init_eeprom ( struct natsemi_private *np ) { * this region. Currently it is not working. But with some * efforts it can. */ - nvo_init ( &np->nvo, &np->eeprom.nvs, 0x0c, 0x68, NULL ); + nvo_init ( &np->nvo, &np->eeprom.nvs, 0x0c, 0x68, NULL, NULL ); } /** diff --git a/src/drivers/net/rtl8139.c b/src/drivers/net/rtl8139.c index e97829f0..7cc1de2f 100644 --- a/src/drivers/net/rtl8139.c +++ b/src/drivers/net/rtl8139.c @@ -288,7 +288,7 @@ static void rtl_init_eeprom ( struct net_device *netdev ) { DBGC ( rtl, "rtl8139 %p EEPROM in use for VPD; cannot use " "for options\n", rtl ); } else { - nvo_init ( &rtl->nvo, &rtl->eeprom.nvs, 0x20, 0x40, + nvo_init ( &rtl->nvo, &rtl->eeprom.nvs, 0x20, 0x40, NULL, &netdev->refcnt ); } } diff --git a/src/include/ipxe/dhcpopts.h b/src/include/ipxe/dhcpopts.h index fe07d903..8fb3d2d7 100644 --- a/src/include/ipxe/dhcpopts.h +++ b/src/include/ipxe/dhcpopts.h @@ -36,6 +36,7 @@ extern void dhcpopt_init ( struct dhcp_options *options, void *data, size_t alloc_len, int ( * realloc ) ( struct dhcp_options *options, size_t len ) ); +extern void dhcpopt_update_used_len ( struct dhcp_options *options ); extern int dhcpopt_no_realloc ( struct dhcp_options *options, size_t len ); #endif /* _IPXE_DHCPOPTS_H */ diff --git a/src/include/ipxe/nvo.h b/src/include/ipxe/nvo.h index 1fdc12cc..995afd74 100644 --- a/src/include/ipxe/nvo.h +++ b/src/include/ipxe/nvo.h @@ -30,12 +30,22 @@ struct nvo_block { size_t len; /** Option-containing data */ void *data; + /** + * Resize non-volatile stored option block + * + * @v nvo Non-volatile options block + * @v len New size + * @ret rc Return status code + */ + int ( * resize ) ( struct nvo_block *nvo, size_t len ); /** DHCP options block */ struct dhcp_options dhcpopts; }; extern void nvo_init ( struct nvo_block *nvo, struct nvs_device *nvs, - size_t address, size_t len, struct refcnt *refcnt ); + size_t address, size_t len, + int ( * resize ) ( struct nvo_block *nvo, size_t len ), + struct refcnt *refcnt ); extern int register_nvo ( struct nvo_block *nvo, struct settings *parent ); extern void unregister_nvo ( struct nvo_block *nvo ); diff --git a/src/net/dhcpopts.c b/src/net/dhcpopts.c index d1330eae..f04b8e71 100644 --- a/src/net/dhcpopts.c +++ b/src/net/dhcpopts.c @@ -402,7 +402,7 @@ int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag, * The "used length" field will be updated based on scanning through * the block to find the end of the options. */ -static void dhcpopt_update_used_len ( struct dhcp_options *options ) { +void dhcpopt_update_used_len ( struct dhcp_options *options ) { struct dhcp_option *option; int offset = 0; ssize_t remaining = options->alloc_len;