xref: /freebsd/sys/dev/sfxge/common/efx_bootcfg.c (revision 10b59a9b4add0320d52c15ce057dd697261e7dfc)
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 */
343