aboutsummaryrefslogtreecommitdiff
path: root/sys/boot/i386/libi386/biospnp.c
blob: 30e55fc893d0e8b802d9e0cd228bb55f1ab9c251 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*-
 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/*
 * PnP BIOS enumerator.
 */

#include <stand.h>
#include <machine/stdarg.h>
#include <bootstrap.h>
#include <isapnp.h>
#include <btxv86.h>


static int	biospnp_init(void);
static void	biospnp_enumerate(void);

struct pnphandler biospnphandler =
{
    "PnP BIOS",
    biospnp_enumerate
};

struct pnp_ICstructure
{
    u_int8_t	pnp_signature[4];
    u_int8_t	pnp_version;
    u_int8_t	pnp_length;
    u_int16_t	pnp_BIOScontrol;
    u_int8_t	pnp_checksum;
    u_int32_t	pnp_eventflag;
    u_int16_t	pnp_rmip;
    u_int16_t	pnp_rmcs;
    u_int16_t	pnp_pmip;
    u_int32_t	pnp_pmcs;
    u_int8_t	pnp_OEMdev[4];
    u_int16_t	pnp_rmds;
    u_int32_t	pnp_pmds;
} __packed;

struct pnp_devNode 
{
    u_int16_t	dn_size;
    u_int8_t	dn_handle;
    u_int8_t	dn_id[4];
    u_int8_t	dn_type[3];
    u_int16_t	dn_attrib;
    u_int8_t	dn_data[1];
} __packed;

struct pnp_isaConfiguration
{
    u_int8_t	ic_revision;
    u_int8_t	ic_nCSN;
    u_int16_t	ic_rdport;
    u_int16_t	ic_reserved;
} __packed;

static struct pnp_ICstructure	*pnp_Icheck = NULL;
static u_int16_t		pnp_NumNodes;
static u_int16_t		pnp_NodeSize;

static void	biospnp_scanresdata(struct pnpinfo *pi, struct pnp_devNode *dn);
static int	biospnp_call(int func, const char *fmt, ...);

#define vsegofs(vptr)	(((u_int32_t)VTOPSEG(vptr) << 16) + VTOPOFF(vptr))

typedef void    v86bios_t(u_int32_t, u_int32_t, u_int32_t, u_int32_t);
v86bios_t	*v86bios = (v86bios_t *)v86int;

#define	biospnp_f00(NumNodes, NodeSize)			biospnp_call(0x00, "ll", NumNodes, NodeSize)
#define biospnp_f01(Node, devNodeBuffer, Control)	biospnp_call(0x01, "llw", Node, devNodeBuffer, Control)
#define biospnp_f40(Configuration)			biospnp_call(0x40, "l", Configuration)

/* PnP BIOS return codes */
#define PNP_SUCCESS			0x00
#define PNP_FUNCTION_NOT_SUPPORTED	0x80

/*
 * Initialisation: locate the PnP BIOS, test that we can call it.
 * Returns nonzero if the PnP BIOS is not usable on this system.
 */
static int
biospnp_init(void)
{
    struct pnp_isaConfiguration	icfg;
    char			*sigptr;
    int				result;
    
    /* Search for the $PnP signature */
    pnp_Icheck = NULL;
    for (sigptr = PTOV(0xf0000); sigptr < PTOV(0xfffff); sigptr += 16)
	if (!bcmp(sigptr, "$PnP", 4)) {
	    pnp_Icheck = (struct pnp_ICstructure *)sigptr;
	    break;
	}
	
    /* No signature, no BIOS */
    if (pnp_Icheck == NULL)
	return(1);

    /*
     * Fetch the system table parameters as a test of the BIOS
     */
    result = biospnp_f00(vsegofs(&pnp_NumNodes), vsegofs(&pnp_NodeSize));
    if (result != PNP_SUCCESS) {
	return(1);
    }

    /*
     * Look for the PnP ISA configuration table 
     */
    result = biospnp_f40(vsegofs(&icfg));
    switch (result) {
    case PNP_SUCCESS:
	/* If the BIOS found some PnP devices, take its hint for the read port */
	if ((icfg.ic_revision == 1) && (icfg.ic_nCSN > 0))
	    isapnp_readport = icfg.ic_rdport;
	break;
    case PNP_FUNCTION_NOT_SUPPORTED:
	/* The BIOS says there is no ISA bus (should we trust that this works?) */
	printf("PnP BIOS claims no ISA bus\n");
	isapnp_readport = -1;
	break;
    }
    return(0);
}

static void
biospnp_enumerate(void)
{
    u_int8_t		Node;
    struct pnp_devNode	*devNodeBuffer;
    int			result;
    struct pnpinfo	*pi;
    int			count;

    /* Init/check state */
    if (biospnp_init())
	return;

    devNodeBuffer = (struct pnp_devNode *)alloca(pnp_NodeSize);
    Node = 0;
    count = 1000;
    while((Node != 0xff) && (count-- > 0)) {
	result = biospnp_f01(vsegofs(&Node), vsegofs(devNodeBuffer), 0x1);
	if (result != PNP_SUCCESS) {
	    printf("PnP BIOS node %d: error 0x%x\n", Node, result);
	} else {
	    pi = pnp_allocinfo();
	    pnp_addident(pi, pnp_eisaformat(devNodeBuffer->dn_id));
	    biospnp_scanresdata(pi, devNodeBuffer);
	    pnp_addinfo(pi);
	}
    }
}

/*
 * Scan the resource data in the node's data area for compatible device IDs
 * and descriptions.
 */
static void
biospnp_scanresdata(struct pnpinfo *pi, struct pnp_devNode *dn)
{
    u_int	tag, i, rlen, dlen;
    u_int8_t	*p;
    char	*str;

    p = dn->dn_data;			/* point to resource data */
    dlen = dn->dn_size - (p - (u_int8_t *)dn);	/* length of resource data */

    for (i = 0; i < dlen; i+= rlen) {
	tag = p[i];
	i++;
	if (PNP_RES_TYPE(tag) == 0) {
	    rlen = PNP_SRES_LEN(tag);
	    /* small resource */
	    switch (PNP_SRES_NUM(tag)) {

	    case COMP_DEVICE_ID:
		/* got a compatible device ID */
		pnp_addident(pi, pnp_eisaformat(p + i));
		break;
		
	    case END_TAG:
		return;
	    }
	} else {
	    /* large resource */
	    rlen = *(u_int16_t *)(p + i);
	    i += sizeof(u_int16_t);
	    
	    switch(PNP_LRES_NUM(tag)) {

	    case ID_STRING_ANSI:
		str = malloc(rlen + 1);
		bcopy(p + i, str, rlen);
		str[rlen] = 0;
		if (pi->pi_desc == NULL) {
		    pi->pi_desc = str;
		} else {
		    free(str);
		}
		break;
	    }
	}
    }
}


/*
 * Make a 16-bit realmode PnP BIOS call.
 *
 * The first argument passed is the function number, the last is the
 * BIOS data segment selector.  Intermediate arguments may be 16 or 
 * 32 bytes in length, and are described by the format string.
 *
 * Arguments to the BIOS functions must be packed on the stack, hence
 * this evil.
 */
static int
biospnp_call(int func, const char *fmt, ...)
{
    va_list	ap;
    const char	*p;
    u_int8_t	*argp;
    u_int32_t	args[4];
    u_int32_t	i;

    /* function number first */
    argp = (u_int8_t *)args;
    *(u_int16_t *)argp = func;
    argp += sizeof(u_int16_t);

    /* take args according to format */
    va_start(ap, fmt);
    for (p = fmt; *p != 0; p++) {
	switch(*p) {

	case 'w':
	    i = va_arg(ap, u_int);
	    *(u_int16_t *)argp = i;
	    argp += sizeof(u_int16_t);
	    break;
	    
	case 'l':
	    i = va_arg(ap, u_int32_t);
	    *(u_int32_t *)argp = i;
	    argp += sizeof(u_int32_t);
	    break;
	}
    }
    va_end(ap);

    /* BIOS segment last */
    *(u_int16_t *)argp = pnp_Icheck->pnp_rmds;
    argp += sizeof(u_int16_t);

    /* prepare for call */
    v86.ctl = V86_ADDR | V86_CALLF; 
    v86.addr = ((u_int32_t)pnp_Icheck->pnp_rmcs << 16) + pnp_Icheck->pnp_rmip;
    
    /* call with packed stack and return */
    v86bios(args[0], args[1], args[2], args[3]);
    return(v86.eax & 0xffff);
}