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 "efx.h"
32 #include "efx_impl.h"
33 
34 #if EFSYS_OPT_BOOTCFG
35 
36 /*
37  * Maximum size of BOOTCFG block across all nics as understood by SFCgPXE.
38  * A multiple of 0x100 so trailing 0xff characters don't contrinbute to the
39  * checksum.
40  */
41 #define	BOOTCFG_MAX_SIZE 0x1000
42 
43 #define	DHCP_END (uint8_t)0xff
44 #define	DHCP_PAD (uint8_t)0
45 
46 static	__checkReturn		uint8_t
efx_bootcfg_csum(__in efx_nic_t * enp,__in_bcount (size)caddr_t data,__in size_t size)47 efx_bootcfg_csum(
48 	__in			efx_nic_t *enp,
49 	__in_bcount(size)	caddr_t data,
50 	__in			size_t size)
51 {
52 	_NOTE(ARGUNUSED(enp))
53 
54 	unsigned int pos;
55 	uint8_t checksum = 0;
56 
57 	for (pos = 0; pos < size; pos++)
58 		checksum += data[pos];
59 	return (checksum);
60 }
61 
62 static	__checkReturn		efx_rc_t
efx_bootcfg_verify(__in efx_nic_t * enp,__in_bcount (size)caddr_t data,__in size_t size,__out_opt size_t * usedp)63 efx_bootcfg_verify(
64 	__in			efx_nic_t *enp,
65 	__in_bcount(size)	caddr_t data,
66 	__in			size_t size,
67 	__out_opt		size_t *usedp)
68 {
69 	size_t offset = 0;
70 	size_t used = 0;
71 	efx_rc_t rc;
72 
73 	/* Start parsing tags immediatly after the checksum */
74 	for (offset = 1; offset < size; ) {
75 		uint8_t tag;
76 		uint8_t length;
77 
78 		/* Consume tag */
79 		tag = data[offset];
80 		if (tag == DHCP_END) {
81 			offset++;
82 			used = offset;
83 			break;
84 		}
85 		if (tag == DHCP_PAD) {
86 			offset++;
87 			continue;
88 		}
89 
90 		/* Consume length */
91 		if (offset + 1 >= size) {
92 			rc = ENOSPC;
93 			goto fail1;
94 		}
95 		length = data[offset + 1];
96 
97 		/* Consume *length */
98 		if (offset + 1 + length >= size) {
99 			rc = ENOSPC;
100 			goto fail2;
101 		}
102 
103 		offset += 2 + length;
104 		used = offset;
105 	}
106 
107 	/* Checksum the entire sector, including bytes after any DHCP_END */
108 	if (efx_bootcfg_csum(enp, data, size) != 0) {
109 		rc = EINVAL;
110 		goto fail3;
111 	}
112 
113 	if (usedp != NULL)
114 		*usedp = used;
115 
116 	return (0);
117 
118 fail3:
119 	EFSYS_PROBE(fail3);
120 fail2:
121 	EFSYS_PROBE(fail2);
122 fail1:
123 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
124 
125 	return (rc);
126 }
127 
128 				efx_rc_t
efx_bootcfg_read(__in efx_nic_t * enp,__out_bcount (size)caddr_t data,__in size_t size)129 efx_bootcfg_read(
130 	__in			efx_nic_t *enp,
131 	__out_bcount(size)	caddr_t data,
132 	__in			size_t size)
133 {
134 	uint8_t *payload = NULL;
135 	size_t used_bytes;
136 	size_t sector_length;
137 	efx_rc_t rc;
138 
139 	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &sector_length);
140 	if (rc != 0)
141 		goto fail1;
142 
143 	/*
144 	 * We need to read the entire BOOTCFG area to ensure we read all the
145 	 * tags, because legacy bootcfg sectors are not guaranteed to end with
146 	 * a DHCP_END character. If the user hasn't supplied a sufficiently
147 	 * large buffer then use our own buffer.
148 	 */
149 	if (sector_length > BOOTCFG_MAX_SIZE)
150 		sector_length = BOOTCFG_MAX_SIZE;
151 	if (sector_length > size) {
152 		EFSYS_KMEM_ALLOC(enp->en_esip, sector_length, payload);
153 		if (payload == NULL) {
154 			rc = ENOMEM;
155 			goto fail2;
156 		}
157 	} else
158 		payload = (uint8_t *)data;
159 
160 	if ((rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, NULL)) != 0)
161 		goto fail3;
162 
163 	rc = efx_nvram_read_chunk(enp, EFX_NVRAM_BOOTROM_CFG, 0,
164 				    (caddr_t)payload, sector_length);
165 
166 	efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
167 
168 	if (rc != 0)
169 		goto fail4;
170 
171 	/* Verify that the area is correctly formatted and checksummed */
172 	rc = efx_bootcfg_verify(enp, (caddr_t)payload, sector_length,
173 				    &used_bytes);
174 	if (rc != 0 || used_bytes == 0) {
175 		payload[0] = (uint8_t)~DHCP_END;
176 		payload[1] = DHCP_END;
177 		used_bytes = 2;
178 	}
179 
180 	EFSYS_ASSERT(used_bytes >= 2);	/* checksum and DHCP_END */
181 	EFSYS_ASSERT(used_bytes <= sector_length);
182 
183 	/*
184 	 * Legacy bootcfg sectors don't terminate with a DHCP_END character.
185 	 * Modify the returned payload so it does. BOOTCFG_MAX_SIZE is by
186 	 * definition large enough for any valid (per-port) bootcfg sector,
187 	 * so reinitialise the sector if there isn't room for the character.
188 	 */
189 	if (payload[used_bytes - 1] != DHCP_END) {
190 		if (used_bytes + 1 > sector_length) {
191 			payload[0] = 0;
192 			used_bytes = 1;
193 		}
194 
195 		payload[used_bytes] = DHCP_END;
196 		++used_bytes;
197 	}
198 
199 	/*
200 	 * Verify that the user supplied buffer is large enough for the
201 	 * entire used bootcfg area, then copy into the user supplied buffer.
202 	 */
203 	if (used_bytes > size) {
204 		rc = ENOSPC;
205 		goto fail5;
206 	}
207 	if (sector_length > size) {
208 		(void) memcpy(data, payload, used_bytes);
209 		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
210 	}
211 
212 	/* Zero out the unused portion of the user buffer */
213 	if (used_bytes < size)
214 		(void) memset(data + used_bytes, 0, size - used_bytes);
215 
216 	/*
217 	 * The checksum includes trailing data after any DHCP_END character,
218 	 * which we've just modified (by truncation or appending DHCP_END).
219 	 */
220 	data[0] -= efx_bootcfg_csum(enp, data, size);
221 
222 	return (0);
223 
224 fail5:
225 	EFSYS_PROBE(fail5);
226 fail4:
227 	EFSYS_PROBE(fail4);
228 fail3:
229 	EFSYS_PROBE(fail3);
230 
231 	if (sector_length > size)
232 		EFSYS_KMEM_FREE(enp->en_esip, sector_length, payload);
233 fail2:
234 	EFSYS_PROBE(fail2);
235 fail1:
236 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
237 
238 	return (rc);
239 }
240 
241 				efx_rc_t
efx_bootcfg_write(__in efx_nic_t * enp,__in_bcount (size)caddr_t data,__in size_t size)242 efx_bootcfg_write(
243 	__in			efx_nic_t *enp,
244 	__in_bcount(size)	caddr_t data,
245 	__in			size_t size)
246 {
247 	uint8_t *chunk;
248 	uint8_t checksum;
249 	size_t sector_length;
250 	size_t chunk_length;
251 	size_t used_bytes;
252 	size_t offset;
253 	size_t remaining;
254 	efx_rc_t rc;
255 
256 	rc = efx_nvram_size(enp, EFX_NVRAM_BOOTROM_CFG, &sector_length);
257 	if (rc != 0)
258 		goto fail1;
259 
260 	if (sector_length > BOOTCFG_MAX_SIZE)
261 		sector_length = BOOTCFG_MAX_SIZE;
262 
263 	if ((rc = efx_bootcfg_verify(enp, data, size, &used_bytes)) != 0)
264 		goto fail2;
265 
266 	/* The caller *must* terminate their block with a DHCP_END character */
267 	EFSYS_ASSERT(used_bytes >= 2);		/* checksum and DHCP_END */
268 	if ((uint8_t)data[used_bytes - 1] != DHCP_END) {
269 		rc = ENOENT;
270 		goto fail3;
271 	}
272 
273 	/* Check that the hardware has support for this much data */
274 	if (used_bytes > MIN(sector_length, BOOTCFG_MAX_SIZE)) {
275 		rc = ENOSPC;
276 		goto fail4;
277 	}
278 
279 	rc = efx_nvram_rw_start(enp, EFX_NVRAM_BOOTROM_CFG, &chunk_length);
280 	if (rc != 0)
281 		goto fail5;
282 
283 	EFSYS_KMEM_ALLOC(enp->en_esip, chunk_length, chunk);
284 	if (chunk == NULL) {
285 		rc = ENOMEM;
286 		goto fail6;
287 	}
288 
289 	if ((rc = efx_nvram_erase(enp, EFX_NVRAM_BOOTROM_CFG)) != 0)
290 		goto fail7;
291 
292 	/*
293 	 * Write the entire sector_length bytes of data in chunks. Zero out
294 	 * all data following the DHCP_END, and adjust the checksum
295 	 */
296 	checksum = efx_bootcfg_csum(enp, data, used_bytes);
297 	for (offset = 0; offset < sector_length; offset += remaining) {
298 		remaining = MIN(chunk_length, sector_length - offset);
299 
300 		/* Fill chunk */
301 		(void) memset(chunk, 0x0, chunk_length);
302 		if (offset < used_bytes)
303 			(void) memcpy(chunk, data + offset,
304 			    MIN(remaining, used_bytes - offset));
305 
306 		/* Adjust checksum */
307 		if (offset == 0)
308 			chunk[0] -= checksum;
309 
310 		if ((rc = efx_nvram_write_chunk(enp, EFX_NVRAM_BOOTROM_CFG,
311 			    offset, (caddr_t)chunk, remaining)) != 0)
312 			goto fail8;
313 	}
314 
315 	efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
316 
317 	EFSYS_KMEM_FREE(enp->en_esip, chunk_length, chunk);
318 
319 	return (0);
320 
321 fail8:
322 	EFSYS_PROBE(fail8);
323 fail7:
324 	EFSYS_PROBE(fail7);
325 
326 	EFSYS_KMEM_FREE(enp->en_esip, chunk_length, chunk);
327 fail6:
328 	EFSYS_PROBE(fail6);
329 
330 	efx_nvram_rw_finish(enp, EFX_NVRAM_BOOTROM_CFG);
331 fail5:
332 	EFSYS_PROBE(fail5);
333 fail4:
334 	EFSYS_PROBE(fail4);
335 fail3:
336 	EFSYS_PROBE(fail3);
337 fail2:
338 	EFSYS_PROBE(fail2);
339 fail1:
340 	EFSYS_PROBE1(fail1, efx_rc_t, rc);
341 
342 	return (rc);
343 }
344 
345 #endif	/* EFSYS_OPT_BOOTCFG */
346