1 /*
   2  * Copyright 2009 Solarflare Communications Inc.  All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  * 1. Redistributions of source code must retain the above copyright
   8  *    notice, this list of conditions and the following disclaimer.
   9  * 2. Redistributions in binary form must reproduce the above copyright
  10  *    notice, this list of conditions and the following disclaimer in the
  11  *    documentation and/or other materials provided with the distribution.
  12  *
  13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS AND
  14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  23  * SUCH DAMAGE.
  24  */
  25 
  26 #include "efsys.h"
  27 #include "efx.h"
  28 #include "efx_types.h"
  29 #include "efx_impl.h"
  30 
  31 #if EFSYS_OPT_BOOTCFG
  32 
  33 /*
  34  * Maximum size of BOOTCFG block across all nics as understood by SFCgPXE.
  35  * A multiple of 0x100 so trailing 0xff characters don't contrinbute to the
  36  * checksum.
  37  */
  38 #define BOOTCFG_MAX_SIZE 0x1000
  39 
  40 #define DHCP_END (uint8_t)0xff
  41 #define DHCP_PAD (uint8_t)0
  42 
  43 static  __checkReturn           uint8_t
  44 efx_bootcfg_csum(
  45         __in                    efx_nic_t *enp,
  46         __in_bcount(size)       caddr_t data,
  47         __in                    size_t size)
  48 {
  49         _NOTE(ARGUNUSED(enp))
  50 
  51         unsigned int pos;
  52         uint8_t checksum = 0;
  53 
  54         for (pos = 0; pos < size; pos++)
  55                 checksum += data[pos];
  56         return (checksum);
  57 }
  58 
  59 static  __checkReturn           int
  60 efx_bootcfg_verify(
  61         __in                    efx_nic_t *enp,
  62         __in_bcount(size)       caddr_t data,
  63         __in                    size_t size,
  64         __out                   size_t *usedp)
  65 {
  66         size_t offset = 0;
  67         size_t used = 0;
  68         int rc;
  69 
  70         /* Start parsing tags immediatly after the checksum */
  71         for (offset = 1; offset < size; ) {
  72                 uint8_t tag;
  73                 uint8_t length;
  74 
  75                 /* Consume tag */
  76                 tag = data[offset];
  77                 if (tag == DHCP_END) {
  78                         offset++;
  79                         used = offset;
  80                         break;
  81                 }
  82                 if (tag == DHCP_PAD) {
  83                         offset++;
  84                         continue;
  85                 }
  86 
  87                 /* Consume length */
  88                 if (offset + 1 >= size) {
  89                         rc = ENOSPC;
  90                         goto fail1;
  91                 }
  92                 length = data[offset + 1];
  93 
  94                 /* Consume *length */
  95                 if (offset + 1 + length >= size) {
  96                         rc = ENOSPC;
  97                         goto fail2;
  98                 }
  99 
 100                 offset += 2 + length;
 101                 used = offset;
 102         }
 103 
 104         /* Checksum the entire sector, including bytes after any DHCP_END */
 105         if (efx_bootcfg_csum(enp, data, size) != 0) {
 106                 rc = EINVAL;
 107                 goto fail3;
 108         }
 109 
 110         if (usedp != NULL)
 111                 *usedp = used;
 112 
 113         return (0);
 114 
 115 fail3:
 116         EFSYS_PROBE(fail3);
 117 fail2:
 118         EFSYS_PROBE(fail2);
 119 fail1:
 120         EFSYS_PROBE1(fail1, int, rc);
 121 
 122         return (rc);
 123 }
 124 
 125                                 int
 126 efx_bootcfg_read(
 127         __in                    efx_nic_t *enp,
 128         __out_bcount(size)      caddr_t data,
 129         __in                    size_t size)
 130 {
 131         uint8_t *payload = NULL;
 132         size_t used_bytes;
 133         size_t sector_length;
 134         int rc;
 135 
 136         rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &sector_length);
 137         if (rc != 0)
 138                 goto fail1;
 139 
 140         /*
 141          * We need to read the entire BOOTCFG area to ensure we read all the
 142          * tags, because legacy bootcfg sectors are not guaranteed to end with
 143          * a DHCP_END character. If the user hasn't supplied a sufficiently
 144          * large buffer then use our own buffer.
 145          */
 146         if (sector_length > BOOTCFG_MAX_SIZE)
 147                 sector_length = BOOTCFG_MAX_SIZE;
 148         if (sector_length > size) {
 149                 EFSYS_KMEM_ALLOC(enp->en_esip, sector_length, payload);
 150                 if (payload == NULL) {
 151                         rc = ENOMEM;
 152                         goto fail2;
 153                 }
 154         } else
 155                 payload = (uint8_t *)data;
 156 
 157         if ((rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
 158                 goto fail3;
 159 
 160         rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, 0,
 161                                     (caddr_t)payload, sector_length);
 162 
 163         efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
 164 
 165         if (rc != 0)
 166                 goto fail4;
 167 
 168         /* Verify that the area is correctly formatted and checksummed */
 169         rc = efx_bootcfg_verify(enp, (caddr_t)payload, sector_length,
 170                                     &used_bytes);
 171         if (rc != 0 || used_bytes == 0) {
 172                 payload[0] = (uint8_t)~DHCP_END;
 173                 payload[1] = DHCP_END;
 174                 used_bytes = 2;
 175         }
 176 
 177         EFSYS_ASSERT(used_bytes >= 2);       /* checksum and DHCP_END */
 178         EFSYS_ASSERT(used_bytes <= sector_length);
 179 
 180         /*
 181          * Legacy bootcfg sectors don't terminate with a DHCP_END character.
 182          * Modify the returned payload so it does. BOOTCFG_MAX_SIZE is by
 183          * definition large enough for any valid (per-port) bootcfg sector,
 184          * so reinitialise the sector if there isn't room for the character.
 185          */
 186         if (payload[used_bytes - 1] != DHCP_END) {
 187                 if (used_bytes + 1 > sector_length) {
 188                         payload[0] = 0;
 189                         used_bytes = 1;
 190                 }
 191 
 192                 payload[used_bytes] = DHCP_END;
 193                 ++used_bytes;
 194         }
 195 
 196         /*
 197          * Verify that the user supplied buffer is large enough for the
 198          * entire used bootcfg area, then copy into the user supplied buffer.
 199          */
 200         if (used_bytes > size) {
 201                 rc = ENOSPC;
 202                 goto fail5;
 203         }
 204         if (sector_length > size) {
 205                 memcpy(data, payload, used_bytes);
 206                 EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
 207         }
 208 
 209         /* Zero out the unused portion of the user buffer */
 210         if (used_bytes < size)
 211                 (void) memset(data + used_bytes, 0, size - used_bytes);
 212 
 213         /*
 214          * The checksum includes trailing data after any DHCP_END character,
 215          * which we've just modified (by truncation or appending DHCP_END).
 216          */
 217         data[0] -= efx_bootcfg_csum(enp, data, size);
 218 
 219         return (0);
 220 
 221 fail5:
 222         EFSYS_PROBE(fail5);
 223 fail4:
 224         EFSYS_PROBE(fail4);
 225 fail3:
 226         EFSYS_PROBE(fail3);
 227 
 228         if (sector_length > size)
 229                 EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
 230 fail2:
 231         EFSYS_PROBE(fail2);
 232 fail1:
 233         EFSYS_PROBE1(fail1, int, rc);
 234 
 235         return (rc);
 236 }
 237 
 238                                 int
 239 efx_bootcfg_write(
 240         __in                    efx_nic_t *enp,
 241         __in_bcount(size)       caddr_t data,
 242         __in                    size_t size)
 243 {
 244         uint8_t *chunk;
 245         uint8_t checksum;
 246         size_t sector_length;
 247         size_t chunk_length;
 248         size_t used_bytes;
 249         size_t offset;
 250         size_t remaining;
 251         int rc;
 252 
 253         rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &sector_length);
 254         if (rc != 0)
 255                 goto fail1;
 256 
 257         if (sector_length > BOOTCFG_MAX_SIZE)
 258                 sector_length = BOOTCFG_MAX_SIZE;
 259 
 260         if ((rc = efx_bootcfg_verify(enp, data, size, &used_bytes)) != 0)
 261                 goto fail2;
 262 
 263         /* The caller *must* terminate their block with a DHCP_END character */
 264         EFSYS_ASSERT(used_bytes >= 2);               /* checksum and DHCP_END */
 265         if ((uint8_t)data[used_bytes - 1] != DHCP_END) {
 266                 rc = ENOENT;
 267                 goto fail3;
 268         }
 269 
 270         /* Check that the hardware has support for this much data */
 271         if (used_bytes > MIN(sector_length, BOOTCFG_MAX_SIZE)) {
 272                 rc = ENOSPC;
 273                 goto fail4;
 274         }
 275 
 276         rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, &chunk_length);
 277         if (rc != 0)
 278                 goto fail5;
 279 
 280         EFSYS_KMEM_ALLOC(enp->en_esip, chunk_length, chunk);
 281         if (chunk == NULL) {
 282                 rc = ENOMEM;
 283                 goto fail6;
 284         }
 285 
 286         if ((rc = efx_nvram_erase(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
 287                 goto fail7;
 288 
 289         /*
 290          * Write the entire sector_length bytes of data in chunks. Zero out
 291          * all data following the DHCP_END, and adjust the checksum
 292          */
 293         checksum = efx_bootcfg_csum(enp, data, used_bytes);
 294         for (offset = 0; offset < sector_length; offset += remaining) {
 295                 remaining = MIN(chunk_length, sector_length - offset);
 296 
 297                 /* Fill chunk */
 298                 (void) memset(chunk, 0x0, chunk_length);
 299                 if (offset < used_bytes)
 300                         memcpy(chunk, data + offset,
 301                             MIN(remaining, used_bytes - offset));
 302 
 303                 /* Adjust checksum */
 304                 if (offset == 0)
 305                         chunk[0] -= checksum;
 306 
 307                 if ((rc = efx_nvram_write_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
 308                             offset, (caddr_t)chunk, remaining)) != 0)
 309                         goto fail8;
 310         }
 311 
 312         efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
 313 
 314         EFSYS_KMEM_FREE(enp->en_esip, chunk_length, chunk);
 315 
 316         return (0);
 317 
 318 fail8:
 319         EFSYS_PROBE(fail8);
 320 fail7:
 321         EFSYS_PROBE(fail7);
 322 
 323         EFSYS_KMEM_FREE(enp->en_esip, chunk_length, chunk);
 324 fail6:
 325         EFSYS_PROBE(fail6);
 326 
 327         efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
 328 fail5:
 329         EFSYS_PROBE(fail5);
 330 fail4:
 331         EFSYS_PROBE(fail4);
 332 fail3:
 333         EFSYS_PROBE(fail3);
 334 fail2:
 335         EFSYS_PROBE(fail2);
 336 fail1:
 337         EFSYS_PROBE1(fail1, int, rc);
 338 
 339         return (rc);
 340 }
 341 
 342 #endif  /* EFSYS_OPT_BOOTCFG */