bioscd.c revision 113083
1303233Sdim/*-
2303233Sdim * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3353358Sdim * Copyright (c) 2001 John H. Baldwin <jhb@FreeBSD.org>
4353358Sdim * All rights reserved.
5353358Sdim *
6303233Sdim * Redistribution and use in source and binary forms, with or without
7303233Sdim * modification, are permitted provided that the following conditions
8303233Sdim * are met:
9303233Sdim * 1. Redistributions of source code must retain the above copyright
10303233Sdim *    notice, this list of conditions and the following disclaimer.
11303233Sdim * 2. Redistributions in binary form must reproduce the above copyright
12303233Sdim *    notice, this list of conditions and the following disclaimer in the
13303233Sdim *    documentation and/or other materials provided with the distribution.
14314564Sdim *
15303233Sdim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16303233Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17303233Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18303233Sdim * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19303233Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20303233Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21303233Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22303233Sdim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23303233Sdim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24303233Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25303233Sdim * SUCH DAMAGE.
26303233Sdim *
27303233Sdim * $FreeBSD: head/sys/boot/i386/libi386/bioscd.c 113083 2003-04-04 16:35:16Z phk $
28303233Sdim */
29303233Sdim
30303233Sdim/*
31303233Sdim * BIOS CD device handling for CD's that have been booted off of via no
32303233Sdim * emulation booting as defined in the El Torito standard.
33303233Sdim *
34303233Sdim * Ideas and algorithms from:
35303233Sdim *
36303233Sdim * - FreeBSD libi386/biosdisk.c
37303233Sdim *
38303233Sdim */
39303233Sdim
40303233Sdim#include <stand.h>
41303233Sdim
42303233Sdim#include <sys/param.h>
43303233Sdim#include <machine/bootinfo.h>
44303233Sdim#include <machine/psl.h>
45303233Sdim
46303233Sdim#include <stdarg.h>
47303233Sdim
48303233Sdim#include <bootstrap.h>
49303233Sdim#include <btxv86.h>
50303233Sdim#include "libi386.h"
51303233Sdim
52303233Sdim#define BIOSCD_SECSIZE		2048
53303233Sdim#define BUFSIZE			(1 * BIOSCD_SECSIZE)
54303233Sdim#define	MAXBCDEV		1
55303233Sdim
56303233Sdim/* Major numbers for devices we frontend for. */
57303233Sdim#define ACDMAJOR		117
58303233Sdim#define	CDMAJOR			15
59303233Sdim
60303233Sdim#ifdef DISK_DEBUG
61303233Sdim# define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
62303233Sdim#else
63303233Sdim# define DEBUG(fmt, args...)
64303233Sdim#endif
65303233Sdim
66303233Sdimstruct specification_packet {
67303233Sdim	u_char	sp_size;
68303233Sdim	u_char	sp_bootmedia;
69303233Sdim	u_char	sp_drive;
70303233Sdim	u_char	sp_controller;
71303233Sdim	u_int	sp_lba;
72303233Sdim	u_short	sp_devicespec;
73303233Sdim	u_short	sp_buffersegment;
74303233Sdim	u_short	sp_loadsegment;
75303233Sdim	u_short	sp_sectorcount;
76303233Sdim	u_short	sp_cylsec;
77303233Sdim	u_char	sp_head;
78303233Sdim};
79303233Sdim
80303233Sdim/*
81303233Sdim * List of BIOS devices, translation from disk unit number to
82303233Sdim * BIOS unit number.
83303233Sdim */
84303233Sdimstatic struct bcinfo {
85303233Sdim	int	bc_unit;		/* BIOS unit number */
86303233Sdim	struct specification_packet bc_sp;
87303233Sdim} bcinfo [MAXBCDEV];
88303233Sdimstatic int nbcinfo = 0;
89303233Sdim
90303233Sdimstatic int	bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
91303233Sdimstatic int	bc_init(void);
92303233Sdimstatic int	bc_strategy(void *devdata, int flag, daddr_t dblk,
93303233Sdim		    size_t size, char *buf, size_t *rsize);
94303233Sdimstatic int	bc_realstrategy(void *devdata, int flag, daddr_t dblk,
95303233Sdim		    size_t size, char *buf, size_t *rsize);
96303233Sdimstatic int	bc_open(struct open_file *f, ...);
97303233Sdimstatic int	bc_close(struct open_file *f);
98303233Sdimstatic void	bc_print(int verbose);
99303233Sdim
100303233Sdimstruct devsw bioscd = {
101303233Sdim	"cd",
102303233Sdim	DEVT_CD,
103303233Sdim	bc_init,
104303233Sdim	bc_strategy,
105303233Sdim	bc_open,
106303233Sdim	bc_close,
107303233Sdim	noioctl,
108303233Sdim	bc_print,
109303233Sdim	NULL
110303233Sdim};
111303233Sdim
112303233Sdim/*
113303233Sdim * Translate between BIOS device numbers and our private unit numbers.
114303233Sdim */
115303233Sdimint
116303233Sdimbc_bios2unit(int biosdev)
117303233Sdim{
118303233Sdim	int i;
119303233Sdim
120303233Sdim	DEBUG("looking for bios device 0x%x", biosdev);
121303233Sdim	for (i = 0; i < nbcinfo; i++) {
122303233Sdim		DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
123303233Sdim		if (bcinfo[i].bc_unit == biosdev)
124303233Sdim			return(i);
125303233Sdim	}
126303233Sdim	return(-1);
127303233Sdim}
128303233Sdim
129303233Sdimint
130303233Sdimbc_unit2bios(int unit)
131303233Sdim{
132303233Sdim	if ((unit >= 0) && (unit < nbcinfo))
133303233Sdim		return(bcinfo[unit].bc_unit);
134303233Sdim	return(-1);
135303233Sdim}
136303233Sdim
137303233Sdim/*
138303233Sdim * We can't quiz, we have to be told what device to use, so this functoin
139303233Sdim * doesn't do anything.  Instead, the loader calls bc_add() with the BIOS
140303233Sdim * device number to add.
141303233Sdim */
142303233Sdimstatic int
143303233Sdimbc_init(void)
144303233Sdim{
145303233Sdim
146303233Sdim	return (0);
147303233Sdim}
148303233Sdim
149303233Sdimint
150303233Sdimbc_add(int biosdev)
151303233Sdim{
152303233Sdim
153303233Sdim	if (nbcinfo >= MAXBCDEV)
154303233Sdim		return (-1);
155303233Sdim	bcinfo[nbcinfo].bc_unit = biosdev;
156303233Sdim	v86.ctl = V86_FLAGS;
157303233Sdim	v86.addr = 0x13;
158303233Sdim	v86.eax = 0x4b01;
159303233Sdim	v86.edx = biosdev;
160303233Sdim	v86.ds = VTOPSEG(&bcinfo[nbcinfo].bc_sp);
161303233Sdim	v86.esi = VTOPOFF(&bcinfo[nbcinfo].bc_sp);
162303233Sdim	v86int();
163303233Sdim	if ((v86.eax & 0xff00) != 0)
164303233Sdim		return (-1);
165303233Sdim
166303233Sdim	printf("BIOS CD is cd%d\n", nbcinfo);
167303233Sdim	nbcinfo++;
168303233Sdim	return(0);
169303233Sdim}
170303233Sdim
171303233Sdim/*
172303233Sdim * Print information about disks
173303233Sdim */
174303233Sdimstatic void
175303233Sdimbc_print(int verbose)
176303233Sdim{
177303233Sdim	int i;
178303233Sdim	char line[80];
179303233Sdim
180303233Sdim	for (i = 0; i < nbcinfo; i++) {
181303233Sdim		sprintf(line, "    cd%d: Device 0x%x\n", i,
182303233Sdim		    bcinfo[i].bc_sp.sp_devicespec);
183303233Sdim		pager_output(line);
184303233Sdim	}
185303233Sdim}
186303233Sdim
187303233Sdim/*
188303233Sdim * Attempt to open the disk described by (dev) for use by (f).
189303233Sdim */
190303233Sdimstatic int
191303233Sdimbc_open(struct open_file *f, ...)
192303233Sdim{
193303233Sdim	va_list ap;
194303233Sdim	struct i386_devdesc *dev;
195303233Sdim	int error;
196303233Sdim
197303233Sdim	va_start(ap, f);
198303233Sdim	dev = va_arg(ap, struct i386_devdesc *);
199303233Sdim	va_end(ap);
200303233Sdim	if (dev->d_kind.bioscd.unit >= nbcinfo) {
201303233Sdim		DEBUG("attempt to open nonexistent disk");
202303233Sdim		return(ENXIO);
203303233Sdim	}
204303233Sdim
205303233Sdim	return(0);
206303233Sdim}
207303233Sdim
208303233Sdimstatic int
209303233Sdimbc_close(struct open_file *f)
210303233Sdim{
211303233Sdim
212303233Sdim	return(0);
213303233Sdim}
214303233Sdim
215303233Sdimstatic int
216303233Sdimbc_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf,
217303233Sdim    size_t *rsize)
218303233Sdim{
219303233Sdim	struct i386_devdesc *dev;
220303233Sdim	int unit;
221303233Sdim	int blks;
222303233Sdim#ifdef BD_SUPPORT_FRAGS
223303233Sdim	char fragbuf[BIOSCD_SECSIZE];
224303233Sdim	size_t fragsize;
225303233Sdim
226303233Sdim	fragsize = size % BIOSCD_SECSIZE;
227303233Sdim#else
228303233Sdim	if (size % BIOSCD_SECSIZE)
229303233Sdim		return (EINVAL);
230303233Sdim#endif
231303233Sdim
232303233Sdim	if (rw != F_READ)
233303233Sdim		return(EROFS);
234303233Sdim	dev = (struct i386_devdesc *)devdata;
235303233Sdim	unit = dev->d_kind.bioscd.unit;
236303233Sdim	blks = size / BIOSCD_SECSIZE;
237303233Sdim	if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
238303233Sdim		return (EINVAL);
239303233Sdim	dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
240303233Sdim	DEBUG("read %d from %d to %p", blks, dblk, buf);
241303233Sdim
242303233Sdim	if (rsize)
243303233Sdim		*rsize = 0;
244303233Sdim	if (blks && bc_read(unit, dblk, blks, buf)) {
245303233Sdim		DEBUG("read error");
246303233Sdim		return (EIO);
247303233Sdim	}
248303233Sdim#ifdef BD_SUPPORT_FRAGS
249303233Sdim	DEBUG("bc_strategy: frag read %d from %d+%d to %p",
250303233Sdim	    fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
251303233Sdim	if (fragsize && bc_read(unit, dblk + blks, 1, fragsize)) {
252303233Sdim		DEBUG("frag read error");
253303233Sdim		return(EIO);
254303233Sdim	}
255303233Sdim	bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
256303233Sdim#endif
257303233Sdim	if (rsize)
258303233Sdim		*rsize = size;
259303233Sdim	return (0);
260303233Sdim}
261303233Sdim
262303233Sdimstatic int
263303233Sdimbc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
264303233Sdim{
265303233Sdim	u_int result, resid, retry;
266303233Sdim	static unsigned short packet[8];
267303233Sdim	int biosdev;
268303233Sdim#ifdef DISK_DEBUG
269303233Sdim	int error;
270303233Sdim#endif
271303233Sdim
272303233Sdim	/* Just in case some idiot actually tries to read -1 blocks... */
273303233Sdim	if (blks < 0)
274303233Sdim		return (-1);
275303233Sdim
276303233Sdim	/* If nothing to do, just return succcess. */
277303233Sdim	if (blks == 0)
278303233Sdim		return (0);
279303233Sdim
280303233Sdim	biosdev = bc_unit2bios(unit);
281303233Sdim	/*
282303233Sdim	 * Loop retrying the operation a couple of times.  The BIOS
283303233Sdim	 * may also retry.
284	 */
285	for (retry = 0; retry < 3; retry++) {
286		/* If retrying, reset the drive */
287		if (retry > 0) {
288			v86.ctl = V86_FLAGS;
289			v86.addr = 0x13;
290			v86.eax = 0;
291			v86.edx = biosdev;
292			v86int();
293		}
294
295		packet[0] = 0x10;
296		packet[1] = blks;
297		packet[2] = VTOPOFF(dest);
298		packet[3] = VTOPSEG(dest);
299		packet[4] = dblk & 0xffff;
300		packet[5] = dblk >> 16;
301		packet[6] = 0;
302		packet[7] = 0;
303		v86.ctl = V86_FLAGS;
304		v86.addr = 0x13;
305		v86.eax = 0x4200;
306		v86.edx = biosdev;
307		v86.ds = VTOPSEG(packet);
308		v86.esi = VTOPOFF(packet);
309		v86int();
310		result = (v86.efl & PSL_C);
311		if (result == 0)
312			break;
313	}
314
315#ifdef DISK_DEBUG
316	error = (v86.eax >> 8) & 0xff;
317#endif
318	DEBUG("%d sectors from %ld to %p (0x%x) %s", blks, dblk, dest,
319	    VTOP(dest), result ? "failed" : "ok");
320	DEBUG("unit %d  status 0x%x",  unit, error);
321
322/*	hexdump(dest, (blks * BIOSCD_SECSIZE)); */
323	return(0);
324}
325
326/*
327 * Return a suitable dev_t value for (dev).
328 */
329int
330bc_getdev(struct i386_devdesc *dev)
331{
332    int biosdev, unit;
333    int major;
334    int rootdev;
335
336    unit = dev->d_kind.bioscd.unit;
337    biosdev = bc_unit2bios(unit);
338    DEBUG("unit %d BIOS device %d", unit, biosdev);
339    if (biosdev == -1)				/* not a BIOS device */
340	return(-1);
341
342    /*
343     * XXX: Need to examine device spec here to figure out if SCSI or
344     * ATAPI.  No idea on how to figure out device number.  All we can
345     * really pass to the kernel is what bus and device on which bus we
346     * were booted from, which dev_t isn't well suited to since those
347     * number don't match to unit numbers very well.  We may just need
348     * to engage in a hack where we pass -C to the boot args if we are
349     * the boot device.
350     */
351    major = ACDMAJOR;
352    unit = 0;	/* XXX */
353
354    /* XXX: Assume partition 'a'. */
355    rootdev = MAKEBOOTDEV(major, 0, 0, unit, 0);
356    DEBUG("dev is 0x%x\n", rootdev);
357    return(rootdev);
358}
359