xref: /freebsd/sys/isa/pnpparse.c (revision 12be6f12e87abc3e1edd3cc6cdc20334bdd63380)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 1999 Doug Rabson
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/malloc.h>
32 #include <sys/module.h>
33 #include <sys/bus.h>
34 
35 #include <machine/stdarg.h>
36 
37 #include <isa/isavar.h>
38 #include <isa/pnpreg.h>
39 #include <isa/pnpvar.h>
40 
41 #define	MAXDEP	8
42 
43 #define I16(p)	((p)[0] + ((p)[1] << 8))
44 #define I32(p)	(I16(p) + (I16((p)+2) << 16))
45 
46 void
47 pnp_printf(uint32_t id, char *fmt, ...)
48 {
49 	va_list ap;
50 
51 	va_start(ap, fmt);
52 	printf("%s: ", pnp_eisaformat(id));
53 	vprintf(fmt, ap);
54 	va_end(ap);
55 }
56 
57 /* parse a single descriptor */
58 
59 static int
60 pnp_parse_desc(device_t dev, u_char tag, u_char *res, int len,
61 	       struct isa_config *config, int ldn)
62 {
63 	char buf[100];
64 	uint32_t id;
65 	uint32_t compat_id;
66 	int temp;
67 
68 	id = isa_get_logicalid(dev);
69 
70 	if (PNP_RES_TYPE(tag) == 0) {
71 
72 		/* Small resource */
73 		switch (PNP_SRES_NUM(tag)) {
74 
75 		case PNP_TAG_VERSION:
76 		case PNP_TAG_VENDOR:
77 			/* these descriptors are quietly ignored */
78 			break;
79 
80 		case PNP_TAG_LOGICAL_DEVICE:
81 		case PNP_TAG_START_DEPENDANT:
82 		case PNP_TAG_END_DEPENDANT:
83 			if (bootverbose)
84 				pnp_printf(id, "unexpected small tag %d\n",
85 					   PNP_SRES_NUM(tag));
86 			/* shouldn't happen; quit now */
87 			return (1);
88 
89 		case PNP_TAG_COMPAT_DEVICE:
90 			/*
91 			 * Got a compatible device id resource.
92 			 * Should keep a list of compat ids in the device.
93 			 */
94 			bcopy(res, &compat_id, 4);
95 			if (isa_get_compatid(dev) == 0)
96 				isa_set_compatid(dev, compat_id);
97 			break;
98 
99 		case PNP_TAG_IRQ_FORMAT:
100 			if (config->ic_nirq == ISA_NIRQ) {
101 				pnp_printf(id, "too many irqs\n");
102 				return (1);
103 			}
104 			if (I16(res) == 0) {
105 				/* a null descriptor */
106 				config->ic_irqmask[config->ic_nirq] = 0;
107 				config->ic_nirq++;
108 				break;
109 			}
110 			if (bootverbose)
111 				pnp_printf(id, "adding irq mask %#02x\n",
112 					   I16(res));
113 			config->ic_irqmask[config->ic_nirq] = I16(res);
114 			config->ic_nirq++;
115 			break;
116 
117 		case PNP_TAG_DMA_FORMAT:
118 			if (config->ic_ndrq == ISA_NDRQ) {
119 				pnp_printf(id, "too many drqs\n");
120 				return (1);
121 			}
122 			if (res[0] == 0) {
123 				/* a null descriptor */
124 				config->ic_drqmask[config->ic_ndrq] = 0;
125 				config->ic_ndrq++;
126 				break;
127 			}
128 			if (bootverbose)
129 				pnp_printf(id, "adding dma mask %#02x\n",
130 					   res[0]);
131 			config->ic_drqmask[config->ic_ndrq] = res[0];
132 			config->ic_ndrq++;
133 			break;
134 
135 		case PNP_TAG_IO_RANGE:
136 			if (config->ic_nport == ISA_NPORT) {
137 				pnp_printf(id, "too many ports\n");
138 				return (1);
139 			}
140 			if (res[6] == 0) {
141 				/* a null descriptor */
142 				config->ic_port[config->ic_nport].ir_start = 0;
143 				config->ic_port[config->ic_nport].ir_end = 0;
144 				config->ic_port[config->ic_nport].ir_size = 0;
145 				config->ic_port[config->ic_nport].ir_align = 0;
146 				config->ic_nport++;
147 				break;
148 			}
149 			if (bootverbose) {
150 				pnp_printf(id, "adding io range "
151 					   "%#x-%#x, size=%#x, "
152 					   "align=%#x\n",
153 					   I16(res + 1),
154 					   I16(res + 3) + res[6]-1,
155 					   res[6], res[5]);
156 			}
157 			config->ic_port[config->ic_nport].ir_start =
158 			    I16(res + 1);
159 			config->ic_port[config->ic_nport].ir_end =
160 			    I16(res + 3) + res[6] - 1;
161 			config->ic_port[config->ic_nport].ir_size = res[6];
162 			if (res[5] == 0) {
163 			    /* Make sure align is at least one */
164 			    res[5] = 1;
165 			}
166 			config->ic_port[config->ic_nport].ir_align = res[5];
167 			config->ic_nport++;
168 			pnp_check_quirks(isa_get_vendorid(dev),
169 					 isa_get_logicalid(dev), ldn, config);
170 			break;
171 
172 		case PNP_TAG_IO_FIXED:
173 			if (config->ic_nport == ISA_NPORT) {
174 				pnp_printf(id, "too many ports\n");
175 				return (1);
176 			}
177 			if (res[2] == 0) {
178 				/* a null descriptor */
179 				config->ic_port[config->ic_nport].ir_start = 0;
180 				config->ic_port[config->ic_nport].ir_end = 0;
181 				config->ic_port[config->ic_nport].ir_size = 0;
182 				config->ic_port[config->ic_nport].ir_align = 0;
183 				config->ic_nport++;
184 				break;
185 			}
186 			if (bootverbose) {
187 				pnp_printf(id, "adding fixed io range "
188 					   "%#x-%#x, size=%#x, "
189 					   "align=%#x\n",
190 					   I16(res),
191 					   I16(res) + res[2] - 1,
192 					   res[2], 1);
193 			}
194 			config->ic_port[config->ic_nport].ir_start = I16(res);
195 			config->ic_port[config->ic_nport].ir_end =
196 			    I16(res) + res[2] - 1;
197 			config->ic_port[config->ic_nport].ir_size = res[2];
198 			config->ic_port[config->ic_nport].ir_align = 1;
199 			config->ic_nport++;
200 			break;
201 
202 		case PNP_TAG_END:
203 			if (bootverbose)
204 				pnp_printf(id, "end config\n");
205 			return (1);
206 
207 		default:
208 			/* Skip this resource */
209 			pnp_printf(id, "unexpected small tag %d\n",
210 				      PNP_SRES_NUM(tag));
211 			break;
212 		}
213 	} else {
214 		/* Large resource */
215 		switch (PNP_LRES_NUM(tag)) {
216 
217 		case PNP_TAG_ID_UNICODE:
218 		case PNP_TAG_LARGE_VENDOR:
219 			/* these descriptors are quietly ignored */
220 			break;
221 
222 		case PNP_TAG_ID_ANSI:
223 			if (len > sizeof(buf) - 1)
224 				len = sizeof(buf) - 1;
225 			bcopy(res, buf, len);
226 
227 			/*
228 			 * Trim trailing spaces and garbage.
229 			 */
230 			while (len > 0 && buf[len - 1] <= ' ')
231 				len--;
232 			buf[len] = '\0';
233 			device_set_desc_copy(dev, buf);
234 			break;
235 
236 		case PNP_TAG_MEMORY_RANGE:
237 			if (config->ic_nmem == ISA_NMEM) {
238 				pnp_printf(id, "too many memory ranges\n");
239 				return (1);
240 			}
241 			if (I16(res + 7) == 0) {
242 				/* a null descriptor */
243 				config->ic_mem[config->ic_nmem].ir_start = 0;
244 				config->ic_mem[config->ic_nmem].ir_end = 0;
245 				config->ic_mem[config->ic_nmem].ir_size = 0;
246 				config->ic_mem[config->ic_nmem].ir_align = 0;
247 				config->ic_nmem++;
248 				break;
249 			}
250 			if (bootverbose) {
251 				temp = I16(res + 7) << 8;
252 				pnp_printf(id, "adding memory range "
253 					   "%#x-%#x, size=%#x, "
254 					   "align=%#x\n",
255 					   I16(res + 1) << 8,
256 					   (I16(res + 3) << 8) + temp - 1,
257 					   temp, I16(res + 5));
258 			}
259 			config->ic_mem[config->ic_nmem].ir_start =
260 			    I16(res + 1) << 8;
261 			config->ic_mem[config->ic_nmem].ir_end =
262 			    (I16(res + 3) << 8) + (I16(res + 7) << 8) - 1;
263 			config->ic_mem[config->ic_nmem].ir_size =
264 			    I16(res + 7) << 8;
265 			config->ic_mem[config->ic_nmem].ir_align = I16(res + 5);
266 			if (!config->ic_mem[config->ic_nmem].ir_align)
267 				config->ic_mem[config->ic_nmem].ir_align =
268 				    0x10000;
269 			config->ic_nmem++;
270 			break;
271 
272 		case PNP_TAG_MEMORY32_RANGE:
273 			if (config->ic_nmem == ISA_NMEM) {
274 				pnp_printf(id, "too many memory ranges\n");
275 				return (1);
276 			}
277 			if (I32(res + 13) == 0) {
278 				/* a null descriptor */
279 				config->ic_mem[config->ic_nmem].ir_start = 0;
280 				config->ic_mem[config->ic_nmem].ir_end = 0;
281 				config->ic_mem[config->ic_nmem].ir_size = 0;
282 				config->ic_mem[config->ic_nmem].ir_align = 0;
283 				config->ic_nmem++;
284 				break;
285 			}
286 			if (bootverbose) {
287 				pnp_printf(id, "adding memory32 range "
288 					   "%#x-%#x, size=%#x, "
289 					   "align=%#x\n",
290 					   I32(res + 1),
291 					   I32(res + 5) + I32(res + 13) - 1,
292 					   I32(res + 13), I32(res + 9));
293 			}
294 			config->ic_mem[config->ic_nmem].ir_start = I32(res + 1);
295 			config->ic_mem[config->ic_nmem].ir_end =
296 			    I32(res + 5) + I32(res + 13) - 1;
297 			config->ic_mem[config->ic_nmem].ir_size = I32(res + 13);
298 			config->ic_mem[config->ic_nmem].ir_align = I32(res + 9);
299 			config->ic_nmem++;
300 			break;
301 
302 		case PNP_TAG_MEMORY32_FIXED:
303 			if (config->ic_nmem == ISA_NMEM) {
304 				pnp_printf(id, "too many memory ranges\n");
305 				return (1);
306 			}
307 			if (I32(res + 5) == 0) {
308 				/* a null descriptor */
309 				config->ic_mem[config->ic_nmem].ir_start = 0;
310 				config->ic_mem[config->ic_nmem].ir_end = 0;
311 				config->ic_mem[config->ic_nmem].ir_size = 0;
312 				config->ic_mem[config->ic_nmem].ir_align = 0;
313 				break;
314 			}
315 			if (bootverbose) {
316 				pnp_printf(id, "adding fixed memory32 range "
317 					   "%#x-%#x, size=%#x\n",
318 					   I32(res + 1),
319 					   I32(res + 1) + I32(res + 5) - 1,
320 					   I32(res + 5));
321 			}
322 			config->ic_mem[config->ic_nmem].ir_start = I32(res + 1);
323 			config->ic_mem[config->ic_nmem].ir_end =
324 			    I32(res + 1) + I32(res + 5) - 1;
325 			config->ic_mem[config->ic_nmem].ir_size = I32(res + 5);
326 			config->ic_mem[config->ic_nmem].ir_align = 1;
327 			config->ic_nmem++;
328 			break;
329 
330 		default:
331 			/* Skip this resource */
332 			pnp_printf(id, "unexpected large tag %d\n",
333 				   PNP_SRES_NUM(tag));
334 			break;
335 		}
336 	}
337 
338 	return (0);
339 }
340 
341 /*
342  * Parse a single "dependent" resource combination.
343  */
344 
345 u_char
346 *pnp_parse_dependant(device_t dev, u_char *resources, int len,
347 		     struct isa_config *config, int ldn)
348 {
349 
350 	return pnp_scan_resources(dev, resources, len, config, ldn,
351 				  pnp_parse_desc);
352 }
353 
354 static void
355 pnp_merge_resources(device_t dev, struct isa_config *from,
356 		    struct isa_config *to)
357 {
358 	device_t parent;
359 	int i;
360 
361 	parent = device_get_parent(dev);
362 	for (i = 0; i < from->ic_nmem; i++) {
363 		if (to->ic_nmem == ISA_NMEM) {
364 			device_printf(parent, "too many memory ranges\n");
365 			return;
366 		}
367 		to->ic_mem[to->ic_nmem] = from->ic_mem[i];
368 		to->ic_nmem++;
369 	}
370 	for (i = 0; i < from->ic_nport; i++) {
371 		if (to->ic_nport == ISA_NPORT) {
372 			device_printf(parent, "too many port ranges\n");
373 			return;
374 		}
375 		to->ic_port[to->ic_nport] = from->ic_port[i];
376 		to->ic_nport++;
377 	}
378 	for (i = 0; i < from->ic_nirq; i++) {
379 		if (to->ic_nirq == ISA_NIRQ) {
380 			device_printf(parent, "too many irq ranges\n");
381 			return;
382 		}
383 		to->ic_irqmask[to->ic_nirq] = from->ic_irqmask[i];
384 		to->ic_nirq++;
385 	}
386 	for (i = 0; i < from->ic_ndrq; i++) {
387 		if (to->ic_ndrq == ISA_NDRQ) {
388 			device_printf(parent, "too many drq ranges\n");
389 			return;
390 		}
391 		to->ic_drqmask[to->ic_ndrq] = from->ic_drqmask[i];
392 		to->ic_ndrq++;
393 	}
394 }
395 
396 /*
397  * Parse resource data for Logical Devices, make a list of available
398  * resource configurations, and add them to the device.
399  *
400  * This function exits as soon as it gets an error reading *ANY*
401  * Resource Data or it reaches the end of Resource Data.
402  */
403 
404 void
405 pnp_parse_resources(device_t dev, u_char *resources, int len, int ldn)
406 {
407 	struct isa_config *configs;
408 	struct isa_config *config;
409 	device_t parent;
410 	int priorities[1 + MAXDEP];
411 	u_char *start;
412 	u_char *p;
413 	u_char tag;
414 	uint32_t id;
415 	int ncfgs;
416 	int l;
417 	int i;
418 
419 	parent = device_get_parent(dev);
420 	id = isa_get_logicalid(dev);
421 
422 	configs = (struct isa_config *)malloc(sizeof(*configs)*(1 + MAXDEP),
423 					      M_DEVBUF, M_NOWAIT | M_ZERO);
424 	if (configs == NULL) {
425 		device_printf(parent, "No memory to parse PNP data\n");
426 		return;
427 	}
428 	config = &configs[0];
429 	priorities[0] = 0;
430 	ncfgs = 1;
431 
432 	p = resources;
433 	start = NULL;
434 	while (len > 0) {
435 		tag = *p++;
436 		len--;
437 		if (PNP_RES_TYPE(tag) == 0) {
438 			/* Small resource */
439 			l = PNP_SRES_LEN(tag);
440 			if (len < l) {
441 				len = 0;
442 				continue;
443 			}
444 			len -= l;
445 
446 			switch (PNP_SRES_NUM(tag)) {
447 
448 			case PNP_TAG_START_DEPENDANT:
449 				if (start != NULL) {
450 					/*
451 					 * Copy the common resources first,
452 					 * then parse the "dependent" resources.
453 					 */
454 					pnp_merge_resources(dev, &configs[0],
455 							    config);
456 					pnp_parse_dependant(dev, start,
457 							    p - start - 1,
458 							    config, ldn);
459 				}
460 				start = p + l;
461 				if (ncfgs > MAXDEP) {
462 					device_printf(parent, "too many dependent configs (%d)\n", MAXDEP);
463 					len = 0;
464 					break;
465 				}
466 				config = &configs[ncfgs];
467 				/*
468 				 * If the priority is not specified,
469 				 * then use the default of 'acceptable'
470 				 */
471 				if (l > 0)
472 					priorities[ncfgs] = p[0];
473 				else
474 					priorities[ncfgs] = 1;
475 				if (bootverbose)
476 					pnp_printf(id, "start dependent (%d)\n",
477 						   priorities[ncfgs]);
478 				ncfgs++;
479 				break;
480 
481 			case PNP_TAG_END_DEPENDANT:
482 				if (start == NULL) {
483 					device_printf(parent,
484 						      "malformed resources\n");
485 					len = 0;
486 					break;
487 				}
488 				/*
489 				 * Copy the common resources first,
490 				 * then parse the "dependent" resources.
491 				 */
492 				pnp_merge_resources(dev, &configs[0], config);
493 				pnp_parse_dependant(dev, start, p - start - 1,
494 						    config, ldn);
495 				start = NULL;
496 				if (bootverbose)
497 					pnp_printf(id, "end dependent\n");
498 				/*
499 				 * Back to the common part; clear it
500 				 * as its contents has already been copied
501 				 * to each dependent.
502 				 */
503 				config = &configs[0];
504 				bzero(config, sizeof(*config));
505 				break;
506 
507 			case PNP_TAG_END:
508 				if (start != NULL) {
509 					device_printf(parent,
510 						      "malformed resources\n");
511 				}
512 				len = 0;
513 				break;
514 
515 			default:
516 				if (start != NULL)
517 					/* defer parsing a dependent section */
518 					break;
519 				if (pnp_parse_desc(dev, tag, p, l, config, ldn))
520 					len = 0;
521 				break;
522 			}
523 			p += l;
524 		} else {
525 			/* Large resource */
526 			if (len < 2) {
527 				len = 0;
528 				break;
529 			}
530 			l = I16(p);
531 			p += 2;
532 			len -= 2;
533 			if (len < l) {
534 				len = 0;
535 				break;
536 			}
537 			len -= l;
538 			if (start == NULL &&
539 			    pnp_parse_desc(dev, tag, p, l, config, ldn)) {
540 				len = 0;
541 				break;
542 			}
543 			p += l;
544 		}
545 	}
546 
547 	if (ncfgs == 1) {
548 		/* Single config without dependants */
549 		ISA_ADD_CONFIG(parent, dev, priorities[0], &configs[0]);
550 		free(configs, M_DEVBUF);
551 		return;
552 	}
553 
554 	for (i = 1; i < ncfgs; i++) {
555 		/*
556 		 * Merge the remaining part of the common resources,
557 		 * if any. Strictly speaking, there shouldn't be common/main
558 		 * resources after the END_DEPENDENT tag.
559 		 */
560 		pnp_merge_resources(dev, &configs[0], &configs[i]);
561 		ISA_ADD_CONFIG(parent, dev, priorities[i], &configs[i]);
562 	}
563 
564 	free(configs, M_DEVBUF);
565 }
566 
567 u_char
568 *pnp_scan_resources(device_t dev, u_char *resources, int len,
569 		    struct isa_config *config, int ldn, pnp_scan_cb *cb)
570 {
571 	u_char *p;
572 	u_char tag;
573 	int l;
574 
575 	p = resources;
576 	while (len > 0) {
577 		tag = *p++;
578 		len--;
579 		if (PNP_RES_TYPE(tag) == 0) {
580 			/* small resource */
581 			l = PNP_SRES_LEN(tag);
582 			if (len < l)
583 				break;
584 			if ((*cb)(dev, tag, p, l, config, ldn))
585 				return (p + l);
586 			if (PNP_SRES_NUM(tag) == PNP_TAG_END)
587 				return (p + l);
588 		} else {
589 			/* large resource */
590 			if (len < 2)
591 				break;
592 			l = I16(p);
593 			p += 2;
594 			len -= 2;
595 			if (len < l)
596 				break;
597 			if ((*cb)(dev, tag, p, l, config, ldn))
598 				return (p + l);
599 		}
600 		p += l;
601 		len -= l;
602 	}
603 	return NULL;
604 }
605