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