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