bioscd.c revision 190146
1/*-
2 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3 * Copyright (c) 2001 John H. Baldwin <jhb@FreeBSD.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD: head/sys/boot/pc98/libpc98/bioscd.c 190146 2009-03-20 12:26:42Z nyan $");
30
31/*
32 * BIOS CD device handling for CD's that have been booted off of via no
33 * emulation booting as defined in the El Torito standard.
34 *
35 * Ideas and algorithms from:
36 *
37 * - FreeBSD libi386/biosdisk.c
38 *
39 */
40
41#include <stand.h>
42
43#include <sys/param.h>
44#include <machine/bootinfo.h>
45#include <machine/psl.h>
46
47#include <stdarg.h>
48
49#include <bootstrap.h>
50#include <btxv86.h>
51#include "libi386.h"
52
53#define BIOSCD_SECSIZE		2048
54#define BUFSIZE			(1 * BIOSCD_SECSIZE)
55#define	MAXBCDEV		1
56
57/* Major numbers for devices we frontend for. */
58#define ACDMAJOR		117
59#define	CDMAJOR			15
60
61#ifdef DISK_DEBUG
62# define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
63#else
64# define DEBUG(fmt, args...)
65#endif
66
67struct specification_packet {
68	u_char	sp_size;
69	u_char	sp_bootmedia;
70	u_char	sp_drive;
71	u_char	sp_controller;
72	u_int	sp_lba;
73	u_short	sp_devicespec;
74	u_short	sp_buffersegment;
75	u_short	sp_loadsegment;
76	u_short	sp_sectorcount;
77	u_short	sp_cylsec;
78	u_char	sp_head;
79};
80
81/*
82 * List of BIOS devices, translation from disk unit number to
83 * BIOS unit number.
84 */
85static struct bcinfo {
86	int	bc_unit;		/* BIOS unit number */
87	struct specification_packet bc_sp;
88} bcinfo [MAXBCDEV];
89static int nbcinfo = 0;
90
91static int	bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
92static int	bc_init(void);
93static int	bc_strategy(void *devdata, int flag, daddr_t dblk,
94		    size_t size, char *buf, size_t *rsize);
95static int	bc_open(struct open_file *f, ...);
96static int	bc_close(struct open_file *f);
97static void	bc_print(int verbose);
98
99struct devsw bioscd = {
100	"cd",
101	DEVT_CD,
102	bc_init,
103	bc_strategy,
104	bc_open,
105	bc_close,
106	noioctl,
107	bc_print,
108	NULL
109};
110
111/*
112 * Translate between BIOS device numbers and our private unit numbers.
113 */
114int
115bc_bios2unit(int biosdev)
116{
117	int i;
118
119	DEBUG("looking for bios device 0x%x", biosdev);
120	for (i = 0; i < nbcinfo; i++) {
121		DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
122		if (bcinfo[i].bc_unit == biosdev)
123			return(i);
124	}
125	return(-1);
126}
127
128int
129bc_unit2bios(int unit)
130{
131	if ((unit >= 0) && (unit < nbcinfo))
132		return(bcinfo[unit].bc_unit);
133	return(-1);
134}
135
136/*
137 * We can't quiz, we have to be told what device to use, so this functoin
138 * doesn't do anything.  Instead, the loader calls bc_add() with the BIOS
139 * device number to add.
140 */
141static int
142bc_init(void)
143{
144
145	return (0);
146}
147
148int
149bc_add(int biosdev)
150{
151
152	if (nbcinfo >= MAXBCDEV)
153		return (-1);
154	bcinfo[nbcinfo].bc_unit = biosdev;
155
156	/* SCSI CD-ROM only */
157	if ((biosdev & 0xf0) != 0xa0)
158		return (-1);
159	if ((((uint32_t *)PTOV(0xA1460))[biosdev & 0x0f] & 0x1f) != 5)
160		return (-1);
161
162	printf("BIOS CD is cd%d\n", nbcinfo);
163	nbcinfo++;
164	return(0);
165}
166
167/*
168 * Print information about disks
169 */
170static void
171bc_print(int verbose)
172{
173	char line[80];
174	int i;
175
176	for (i = 0; i < nbcinfo; i++) {
177		sprintf(line, "    cd%d: Device 0x%x\n", i,
178		    bcinfo[i].bc_sp.sp_devicespec);
179		pager_output(line);
180	}
181}
182
183/*
184 * Attempt to open the disk described by (dev) for use by (f).
185 */
186static int
187bc_open(struct open_file *f, ...)
188{
189	va_list ap;
190	struct i386_devdesc *dev;
191
192	va_start(ap, f);
193	dev = va_arg(ap, struct i386_devdesc *);
194	va_end(ap);
195	if (dev->d_unit >= nbcinfo) {
196		DEBUG("attempt to open nonexistent disk");
197		return(ENXIO);
198	}
199
200	return(0);
201}
202
203static int
204bc_close(struct open_file *f)
205{
206
207	return(0);
208}
209
210static int
211bc_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf,
212    size_t *rsize)
213{
214	struct i386_devdesc *dev;
215	int unit;
216	int blks;
217#ifdef BD_SUPPORT_FRAGS
218	char fragbuf[BIOSCD_SECSIZE];
219	size_t fragsize;
220
221	fragsize = size % BIOSCD_SECSIZE;
222#else
223	if (size % BIOSCD_SECSIZE)
224		return (EINVAL);
225#endif
226
227	if (rw != F_READ)
228		return(EROFS);
229	dev = (struct i386_devdesc *)devdata;
230	unit = dev->d_unit;
231	blks = size / BIOSCD_SECSIZE;
232	if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
233		return (EINVAL);
234	dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
235	DEBUG("read %d from %lld to %p", blks, dblk, buf);
236
237	if (rsize)
238		*rsize = 0;
239	if (blks && bc_read(unit, dblk, blks, buf)) {
240		DEBUG("read error");
241		return (EIO);
242	}
243#ifdef BD_SUPPORT_FRAGS
244	DEBUG("frag read %d from %lld+%d to %p",
245	    fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
246	if (fragsize && bc_read(unit, dblk + blks, 1, fragbuf)) {
247		DEBUG("frag read error");
248		return(EIO);
249	}
250	bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
251#endif
252	if (rsize)
253		*rsize = size;
254	return (0);
255}
256
257/* Max number of sectors to bounce-buffer at a time. */
258#define	CD_BOUNCEBUF	8
259
260static int
261bc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
262{
263	u_int maxfer, resid, result, retry, x;
264	caddr_t bbuf, p, xp;
265	int biosdev;
266#ifdef DISK_DEBUG
267	int error;
268#endif
269
270	/* Just in case some idiot actually tries to read -1 blocks... */
271	if (blks < 0)
272		return (-1);
273
274	/* If nothing to do, just return succcess. */
275	if (blks == 0)
276		return (0);
277
278	/* Decide whether we have to bounce */
279	if (VTOP(dest) >> 20 != 0) {
280		/*
281		 * The destination buffer is above first 1MB of
282		 * physical memory so we have to arrange a suitable
283		 * bounce buffer.
284		 */
285		x = min(CD_BOUNCEBUF, (unsigned)blks);
286		bbuf = alloca(x * BIOSCD_SECSIZE);
287		maxfer = x;
288	} else {
289		bbuf = NULL;
290		maxfer = 0;
291	}
292
293	biosdev = bc_unit2bios(unit);
294	resid = blks;
295	p = dest;
296
297	while (resid > 0) {
298		if (bbuf)
299			xp = bbuf;
300		else
301			xp = p;
302		x = resid;
303		if (maxfer > 0)
304			x = min(x, maxfer);
305
306		/*
307		 * Loop retrying the operation a couple of times.  The BIOS
308		 * may also retry.
309		 */
310		for (retry = 0; retry < 3; retry++) {
311			/* If retrying, reset the drive */
312			if (retry > 0) {
313				v86.ctl = V86_FLAGS;
314				v86.addr = 0x1b;
315				v86.eax = 0x0300 | biosdev;
316				v86int();
317			}
318
319			v86.ctl = V86_FLAGS;
320			v86.addr = 0x1b;
321			v86.eax = 0x0600 | (biosdev & 0x7f);
322			v86.ebx = blks * BIOSCD_SECSIZE;
323			v86.ecx = dblk & 0xffff;
324			v86.edx = (dblk >> 16) & 0xffff;
325			v86.ebp = VTOPOFF(dest);
326			v86.es = VTOPSEG(dest);
327			v86int();
328			result = (v86.efl & PSL_C);
329			if (result == 0)
330				break;
331		}
332
333#ifdef DISK_DEBUG
334		error = (v86.eax >> 8) & 0xff;
335#endif
336		DEBUG("%d sectors from %lld to %p (0x%x) %s", x, dblk, p,
337		    VTOP(p), result ? "failed" : "ok");
338		DEBUG("unit %d  status 0x%x", unit, error);
339		if (bbuf != NULL)
340			bcopy(bbuf, p, x * BIOSCD_SECSIZE);
341		p += (x * BIOSCD_SECSIZE);
342		dblk += x;
343		resid -= x;
344	}
345
346/*	hexdump(dest, (blks * BIOSCD_SECSIZE)); */
347	return(0);
348}
349
350/*
351 * Return a suitable dev_t value for (dev).
352 */
353int
354bc_getdev(struct i386_devdesc *dev)
355{
356    int biosdev, unit, device;
357    int major;
358    int rootdev;
359
360    unit = dev->d_unit;
361    biosdev = bc_unit2bios(unit);
362    DEBUG("unit %d BIOS device %d", unit, biosdev);
363    if (biosdev == -1)				/* not a BIOS device */
364	return(-1);
365
366    device = biosdev & 0xf0;
367    if (device == 0x80)
368	major = ACDMAJOR;
369    else if (device == 0xa0)
370	major = CDMAJOR;
371    else
372	return (-1);
373
374    unit = 0;	/* XXX */
375
376    /* XXX: Assume partition 'a'. */
377    rootdev = MAKEBOOTDEV(major, 0, unit, 0);
378    DEBUG("dev is 0x%x\n", rootdev);
379    return(rootdev);
380}
381