xref: /dragonfly/stand/boot/pc32/libi386/bioscd.c (revision 655933d6)
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  * $FreeBSD: src/sys/boot/i386/libi386/bioscd.c,v 1.5 2003/08/25 23:28:31 obrien Exp $
28  * $DragonFly: src/sys/boot/pc32/libi386/bioscd.c,v 1.6 2006/09/10 01:26:31 dillon Exp $
29  */
30 /*
31  * BIOS CD device handling for CD's that have been booted off of via no
32  * emulation booting as defined in the El Torito standard.
33  *
34  * Ideas and algorithms from:
35  *
36  * - FreeBSD libi386/biosdisk.c
37  *
38  */
39 
40 #include <stand.h>
41 
42 #include <sys/param.h>
43 #include <machine/bootinfo.h>
44 #include <machine/psl.h>
45 
46 #include <stdarg.h>
47 
48 #include <bootstrap.h>
49 #include <btxv86.h>
50 #include "libi386.h"
51 
52 #define BIOSCD_SECSIZE		2048
53 #define BUFSIZE			(1 * BIOSCD_SECSIZE)
54 #define	MAXBCDEV		1
55 
56 /* Major numbers for devices we frontend for. */
57 #define ACDMAJOR		117
58 #define	CDMAJOR			15
59 
60 #ifdef DISK_DEBUG
61 # define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
62 #else
63 # define DEBUG(fmt, args...)
64 #endif
65 
66 struct specification_packet {
67 	u_char	sp_size;
68 	u_char	sp_bootmedia;
69 	u_char	sp_drive;
70 	u_char	sp_controller;
71 	u_int	sp_lba;
72 	u_short	sp_devicespec;
73 	u_short	sp_buffersegment;
74 	u_short	sp_loadsegment;
75 	u_short	sp_sectorcount;
76 	u_short	sp_cylsec;
77 	u_char	sp_head;
78 };
79 
80 /*
81  * List of BIOS devices, translation from disk unit number to
82  * BIOS unit number.
83  */
84 static struct bcinfo {
85 	int	bc_unit;		/* BIOS unit number */
86 	struct specification_packet bc_sp;
87 } bcinfo [MAXBCDEV];
88 static int nbcinfo = 0;
89 
90 static int	bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
91 static int	bc_init(void);
92 static int	bc_strategy(void *devdata, int flag, daddr_t dblk,
93 		    size_t size, char *buf, size_t *rsize);
94 static int	bc_open(struct open_file *f, ...);
95 static int	bc_close(struct open_file *f);
96 static void	bc_print(int verbose);
97 
98 struct devsw bioscd = {
99 	"cd",
100 	DEVT_CD,
101 	bc_init,
102 	bc_strategy,
103 	bc_open,
104 	bc_close,
105 	noioctl,
106 	bc_print,
107 	NULL
108 };
109 
110 /*
111  * Translate between BIOS device numbers and our private unit numbers.
112  */
113 int
114 bc_bios2unit(int biosdev)
115 {
116 	int i;
117 
118 	DEBUG("looking for bios device 0x%x", biosdev);
119 	for (i = 0; i < nbcinfo; i++) {
120 		DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
121 		if (bcinfo[i].bc_unit == biosdev)
122 			return(i);
123 	}
124 	return(-1);
125 }
126 
127 int
128 bc_unit2bios(int unit)
129 {
130 	if ((unit >= 0) && (unit < nbcinfo))
131 		return(bcinfo[unit].bc_unit);
132 	return(-1);
133 }
134 
135 /*
136  * We can't quiz, we have to be told what device to use, so this functoin
137  * doesn't do anything.  Instead, the loader calls bc_add() with the BIOS
138  * device number to add.
139  */
140 static int
141 bc_init(void)
142 {
143 
144 	return (0);
145 }
146 
147 int
148 bc_add(int biosdev)
149 {
150 
151 	if (nbcinfo >= MAXBCDEV)
152 		return (-1);
153 	bcinfo[nbcinfo].bc_unit = biosdev;
154 	v86.ctl = V86_FLAGS;
155 	v86.addr = 0x13;
156 	v86.eax = 0x4b01;
157 	v86.edx = biosdev;
158 	v86.ds = VTOPSEG(&bcinfo[nbcinfo].bc_sp);
159 	v86.esi = VTOPOFF(&bcinfo[nbcinfo].bc_sp);
160 	v86int();
161 	if ((v86.eax & 0xff00) != 0)
162 		return (-1);
163 
164 	printf("BIOS CD is cd%d\n", nbcinfo);
165 	nbcinfo++;
166 	return(0);
167 }
168 
169 /*
170  * Print information about disks
171  */
172 static void
173 bc_print(int verbose)
174 {
175 	int i;
176 	char line[80];
177 
178 	for (i = 0; i < nbcinfo; i++) {
179 		sprintf(line, "    cd%d: Device 0x%x\n", i,
180 		    bcinfo[i].bc_sp.sp_devicespec);
181 		pager_output(line);
182 	}
183 }
184 
185 /*
186  * Attempt to open the disk described by (dev) for use by (f).
187  */
188 static int
189 bc_open(struct open_file *f, ...)
190 {
191 	va_list ap;
192 	struct i386_devdesc *dev;
193 
194 	va_start(ap, f);
195 	dev = va_arg(ap, struct i386_devdesc *);
196 	va_end(ap);
197 	if (dev->d_kind.bioscd.unit >= nbcinfo) {
198 		DEBUG("attempt to open nonexistent disk");
199 		return(ENXIO);
200 	}
201 
202 	return(0);
203 }
204 
205 static int
206 bc_close(struct open_file *f)
207 {
208 
209 	return(0);
210 }
211 
212 static int
213 bc_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf,
214     size_t *rsize)
215 {
216 	struct i386_devdesc *dev;
217 	int unit;
218 	int blks;
219 #ifdef BD_SUPPORT_FRAGS
220 #error "xxx broken code xxx"
221 	char fragbuf[BIOSCD_SECSIZE];
222 	size_t fragsize;
223 
224 	fragsize = size % BIOSCD_SECSIZE;
225 #else
226 	if (size % BIOSCD_SECSIZE)
227 		return (EINVAL);
228 #endif
229 
230 	if (rw != F_READ)
231 		return(EROFS);
232 	dev = (struct i386_devdesc *)devdata;
233 	unit = dev->d_kind.bioscd.unit;
234 	blks = size / BIOSCD_SECSIZE;
235 	if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
236 		return (EINVAL);
237 	dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
238 	DEBUG("read %d from %d to %p", blks, dblk, buf);
239 
240 	if (rsize)
241 		*rsize = 0;
242 	if (blks && bc_read(unit, dblk, blks, buf)) {
243 		DEBUG("read error");
244 		return (EIO);
245 	}
246 #ifdef BD_SUPPORT_FRAGS
247 	DEBUG("bc_strategy: frag read %d from %d+%d to %p",
248 	    fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
249 	if (fragsize && bc_read(unit, dblk + blks, 1, fragsize)) {
250 		DEBUG("frag read error");
251 		return(EIO);
252 	}
253 	bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
254 #endif
255 	if (rsize)
256 		*rsize = size;
257 	return (0);
258 }
259 
260 static int
261 bc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
262 {
263 	u_int result, retry;
264 	static unsigned short packet[8];
265 	int biosdev;
266 	int n;
267 #ifdef DISK_DEBUG
268 	int error;
269 #endif
270 
271 	/* Just in case some idiot actually tries to read -1 blocks... */
272 	if (blks < 0)
273 		return (-1);
274 
275 	/* If nothing to do, just return succcess. */
276 	if (blks == 0)
277 		return (0);
278 
279 	biosdev = bc_unit2bios(unit);
280 	/*
281 	 * Loop retrying the operation a couple of times.  The BIOS
282 	 * may also retry.
283 	 */
284 	for (retry = 0; retry < 3; retry++) {
285 		/* If retrying, reset the drive */
286 		if (retry > 0) {
287 			v86.ctl = V86_FLAGS;
288 			v86.addr = 0x13;
289 			v86.eax = 0;
290 			v86.edx = biosdev;
291 			v86int();
292 		}
293 
294 		n = BOUNCEBUF_SIZE / BIOSCD_SECSIZE;
295 		if (n > blks)
296 			n = blks;
297 
298 		packet[0] = 0x10;
299 		packet[1] = n;
300 		packet[2] = VTOPOFF(bounce_base);
301 		packet[3] = VTOPSEG(bounce_base);
302 		packet[4] = dblk & 0xffff;
303 		packet[5] = dblk >> 16;
304 		packet[6] = 0;
305 		packet[7] = 0;
306 		v86.ctl = V86_FLAGS;
307 		v86.addr = 0x13;
308 		v86.eax = 0x4200;
309 		v86.edx = biosdev;
310 		v86.ds = VTOPSEG(packet);
311 		v86.esi = VTOPOFF(packet);
312 		v86int();
313 		result = (v86.efl & PSL_C);
314 		if (result == 0) {
315 			bcopy(bounce_base, dest, n * BIOSCD_SECSIZE);
316 			blks -= n;
317 			dest += n * BIOSCD_SECSIZE;
318 			if (blks == 0)
319 				break;
320 			retry = 0;
321 		}
322 	}
323 
324 #ifdef DISK_DEBUG
325 	error = (v86.eax >> 8) & 0xff;
326 #endif
327 	DEBUG("%d sectors from %ld to %p (0x%x) %s", blks, dblk, dest,
328 	    VTOP(dest), result ? "failed" : "ok");
329 	DEBUG("unit %d  status 0x%x",  unit, error);
330 
331 /*	hexdump(dest, (blks * BIOSCD_SECSIZE)); */
332 	return(0);
333 }
334 
335 /*
336  * Return a suitable cdev_t value for (dev).
337  */
338 int
339 bc_getdev(struct i386_devdesc *dev)
340 {
341     int biosdev, unit;
342     int major;
343     int rootdev;
344 
345     unit = dev->d_kind.bioscd.unit;
346     biosdev = bc_unit2bios(unit);
347     DEBUG("unit %d BIOS device %d", unit, biosdev);
348     if (biosdev == -1)				/* not a BIOS device */
349 	return(-1);
350 
351     /*
352      * XXX: Need to examine device spec here to figure out if SCSI or
353      * ATAPI.  No idea on how to figure out device number.  All we can
354      * really pass to the kernel is what bus and device on which bus we
355      * were booted from, which cdev_t isn't well suited to since those
356      * number don't match to unit numbers very well.  We may just need
357      * to engage in a hack where we pass -C to the boot args if we are
358      * the boot device.
359      */
360     major = ACDMAJOR;
361     unit = 0;	/* XXX */
362 
363     /* XXX: Assume partition 'a'. */
364     rootdev = MAKEBOOTDEV(major, 0, unit, 0);
365     DEBUG("dev is 0x%x\n", rootdev);
366     return(rootdev);
367 }
368