xref: /netbsd-src/sys/arch/atari/atari/disksubr.c (revision b5677b36047b601b9addaaa494a58ceae82c2a6c)
1 /*	$NetBSD: disksubr.c,v 1.40 2009/03/18 16:00:10 cegger Exp $	*/
2 
3 /*
4  * Copyright (c) 1995 Leo Weppelman.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *      This product includes software developed by Leo Weppelman.
18  * 4. The name of the author may not be used to endorse or promote products
19  *    derived from this software without specific prior written permission
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: disksubr.c,v 1.40 2009/03/18 16:00:10 cegger Exp $");
35 
36 #ifndef DISKLABEL_NBDA
37 #define	DISKLABEL_NBDA	/* required */
38 #endif
39 
40 #include "opt_compat_netbsd.h"
41 
42 #include <sys/param.h>
43 #include <sys/systm.h>
44 #include <sys/buf.h>
45 #include <ufs/ufs/dinode.h>
46 #include <ufs/ffs/fs.h>
47 #include <sys/disk.h>
48 #include <sys/disklabel.h>
49 #include <machine/ahdilabel.h>
50 
51 /*
52  * BBSIZE in <ufs/ffs/fs.h> must be greater than
53  * or equal to BBMINSIZE in <machine/disklabel.h>
54  */
55 #if BBSIZE < BBMINSIZE
56 #error BBSIZE smaller than BBMINSIZE
57 #endif
58 
59 static void  ck_label(struct disklabel *, struct cpu_disklabel *);
60 static int   bsd_label(dev_t, void (*)(struct buf *),
61 			struct disklabel *, u_int, u_int *);
62 static int   ahdi_label(dev_t, void (*)(struct buf *),
63 			struct disklabel *, struct cpu_disklabel *);
64 static void  ahdi_to_bsd(struct disklabel *, struct ahdi_ptbl *);
65 static u_int ahdi_getparts(dev_t, void (*)(struct buf *), u_int,
66 					u_int, u_int, struct ahdi_ptbl *);
67 
68 /*
69  * Attempt to read a disk label from a device using the
70  * indicated strategy routine. The label must be partly
71  * set up before this:
72  * secpercyl and anything required in the strategy routine
73  * (e.g. sector size) must be filled in before calling us.
74  * Returns NULL on success and an error string on failure.
75  */
76 const char *
77 readdisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, struct cpu_disklabel *clp)
78 {
79 	int			e;
80 
81 	if (clp != NULL)
82 		memset(clp, 0, sizeof *clp);
83 	else printf("Warning: clp == NULL\n");
84 
85 	/*
86 	 * Give some guaranteed validity to the disk label.
87 	 */
88 	if (lp->d_secsize == 0)
89 		lp->d_secsize = DEV_BSIZE;
90 	if (lp->d_secperunit == 0)
91 		lp->d_secperunit = 0x1fffffff;
92 	if (lp->d_secpercyl == 0)
93 		return("Zero secpercyl");
94 
95 	/*
96 	 * Some parts of the kernel (see scsipi/cd.c for an example)
97 	 * assume that stuff they already had setup in d_partitions
98 	 * is still there after reading the disklabel. Hence the
99 	 * 'if 0'
100 	 */
101 #if 0
102 	memset(lp->d_partitions, 0, sizeof lp->d_partitions);
103 #endif
104 
105 	lp->d_partitions[RAW_PART].p_size = lp->d_secperunit;
106 	lp->d_npartitions                 = RAW_PART + 1;
107 	lp->d_bbsize                      = BBSIZE;
108 	lp->d_sbsize                      = SBLOCKSIZE;
109 
110 #ifdef DISKLABEL_NBDA
111 	/* Try the native NetBSD/Atari format first. */
112 	e = bsd_label(dev, strat, lp, 0, clp != NULL ? &clp->cd_label : NULL);
113 #endif
114 #if 0
115 	/* Other label formats go here. */
116 	if (e > 0)
117 		e = foo_label(dev, strat, lp, ...);
118 #endif
119 #ifdef DISKLABEL_AHDI
120 	/* The unprotected AHDI format comes last. */
121 	if (e > 0 && (clp != NULL))
122 		e = ahdi_label(dev, strat, lp, clp);
123 #endif
124 	if (e < 0)
125 		return("I/O error");
126 
127 	/* Unknown format or uninitialized volume? */
128 	if (e > 0)
129 		uprintf("Warning: unknown disklabel format"
130 			"- assuming empty disk\n");
131 
132 	/* Calulate new checksum. */
133 	lp->d_magic = lp->d_magic2 = DISKMAGIC;
134 	lp->d_checksum = 0;
135 	lp->d_checksum = dkcksum(lp);
136 
137 	return(NULL);
138 }
139 
140 /*
141  * Check new disk label for sensibility before setting it.
142  */
143 int
144 setdisklabel(struct disklabel *olp, struct disklabel *nlp, u_long openmask, struct cpu_disklabel *clp)
145 {
146 	/* special case to allow disklabel to be invalidated */
147 	if (nlp->d_magic == 0xffffffff) {
148 		*olp = *nlp;
149 		return(0);
150 	}
151 
152 	/* sanity clause */
153 	if (nlp->d_secpercyl == 0 || nlp->d_npartitions > MAXPARTITIONS
154 	  || nlp->d_secsize  == 0 || (nlp->d_secsize % DEV_BSIZE) != 0
155 	  || nlp->d_magic != DISKMAGIC || nlp->d_magic2 != DISKMAGIC
156 	  || dkcksum(nlp) != 0)
157 		return(EINVAL);
158 
159 #ifdef DISKLABEL_AHDI
160 	if (clp && clp->cd_bblock)
161 		ck_label(nlp, clp);
162 #endif
163 	while (openmask) {
164 		struct partition *op, *np;
165 		int i = ffs(openmask) - 1;
166 		openmask &= ~(1 << i);
167 		if (i >= nlp->d_npartitions)
168 			return(EBUSY);
169 		op = &olp->d_partitions[i];
170 		np = &nlp->d_partitions[i];
171 		if (np->p_offset != op->p_offset || np->p_size < op->p_size)
172 			return(EBUSY);
173 		/*
174 		 * Copy internally-set partition information
175 		 * if new label doesn't include it.		XXX
176 		 */
177 		if (np->p_fstype == FS_UNUSED && op->p_fstype != FS_UNUSED) {
178 			np->p_fstype = op->p_fstype;
179 			np->p_fsize  = op->p_fsize;
180 			np->p_frag   = op->p_frag;
181 			np->p_cpg    = op->p_cpg;
182 		}
183 	}
184  	nlp->d_checksum = 0;
185  	nlp->d_checksum = dkcksum(nlp);
186 	*olp = *nlp;
187 	return(0);
188 }
189 
190 /*
191  * Write disk label back to device after modification.
192  */
193 int
194 writedisklabel(dev_t dev, void (*strat)(struct buf *), struct disklabel *lp, struct cpu_disklabel *clp)
195 {
196 	struct buf		*bp;
197 	u_int			blk;
198 	int			rv;
199 
200 	blk = clp->cd_bblock;
201 	if (blk == NO_BOOT_BLOCK)
202 		return(ENXIO);
203 
204 	bp = geteblk(BBMINSIZE);
205 	bp->b_dev      = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART);
206 	bp->b_flags    |= B_READ;
207 	bp->b_bcount   = BBMINSIZE;
208 	bp->b_blkno    = blk;
209 	bp->b_cylinder = blk / lp->d_secpercyl;
210 	(*strat)(bp);
211 	rv = biowait(bp);
212 	if (!rv) {
213 		struct bootblock *bb = (struct bootblock *)bp->b_data;
214 		/*
215 		 * Allthough the disk pack label may appear anywhere
216 		 * in the boot block while reading, it is always
217 		 * written at a fixed location.
218 		 */
219 		if (clp->cd_label != LABELOFFSET) {
220 			clp->cd_label = LABELOFFSET;
221 			memset(bb, 0, sizeof(*bb));
222 		}
223 		bb->bb_magic = (blk == 0) ? NBDAMAGIC : AHDIMAGIC;
224 		BBSETLABEL(bb, lp);
225 
226 		bp->b_oflags   &= ~(BO_DONE);
227 		bp->b_flags    &= ~(B_READ);
228 		bp->b_flags    |= B_WRITE;
229 		bp->b_bcount   = BBMINSIZE;
230 		bp->b_blkno    = blk;
231 		bp->b_cylinder = blk / lp->d_secpercyl;
232 		(*strat)(bp);
233 		rv = biowait(bp);
234 	}
235 	brelse(bp, 0);
236 	return(rv);
237 }
238 
239 /*
240  * Read bootblock at block `blkno' and check
241  * if it contains a valid NetBSD disk label.
242  *
243  * Returns:  0 if successful,
244  *          -1 if an I/O error occurred,
245  *          +1 if no valid label was found.
246  */
247 static int
248 bsd_label(dev, strat, label, blkno, offsetp)
249 	dev_t			dev;
250 	void			(*strat)(struct buf *);
251 	struct disklabel	*label;
252 	u_int			blkno,
253 				*offsetp;
254 {
255 	struct buf		*bp;
256 	int			rv;
257 
258 	bp = geteblk(BBMINSIZE);
259 	bp->b_dev      = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART);
260 	bp->b_flags    |= B_READ;
261 	bp->b_bcount   = BBMINSIZE;
262 	bp->b_blkno    = blkno;
263 	bp->b_cylinder = blkno / label->d_secpercyl;
264 	(*strat)(bp);
265 
266 	rv = -1;
267 	if (!biowait(bp)) {
268 		struct bootblock *bb;
269 		u_int32_t   *p, *end;
270 
271 		rv  = 1;
272 		bb  = (struct bootblock *)bp->b_data;
273 		end = (u_int32_t *)((char *)&bb[1] - sizeof(struct disklabel));
274 		for (p = (u_int32_t *)bb; p < end; ++p) {
275 			struct disklabel *dl = (struct disklabel *)&p[1];
276 			/*
277 			 * Compatibility kludge: the boot block magic number is
278 			 * new in 1.1A, in previous versions the disklabel was
279 			 * stored at the end of the boot block (offset 7168).
280 			 */
281 			if (  (  (p[0] == NBDAMAGIC && blkno == 0)
282 			      || (p[0] == AHDIMAGIC && blkno != 0)
283 #ifdef COMPAT_11
284 			      || (char *)dl - (char *)bb == 7168
285 #endif
286 			      )
287 			   && dl->d_npartitions <= MAXPARTITIONS
288 			   && dl->d_magic2 == DISKMAGIC
289 			   && dl->d_magic  == DISKMAGIC
290 		  	   && dkcksum(dl)  == 0
291 			   )	{
292 				if (offsetp != NULL)
293 					*offsetp = (char *)dl - (char *)bb;
294 				*label = *dl;
295 				rv     = 0;
296 				break;
297 			}
298 		}
299 	}
300 	brelse(bp, 0);
301 	return(rv);
302 }
303 
304 #ifdef DISKLABEL_AHDI
305 /*
306  * Check for consistency between the NetBSD partition table
307  * and the AHDI auxiliary root sectors. There's no good reason
308  * to force such consistency, but issuing a warning may help
309  * an inexperienced sysadmin to prevent corruption of AHDI
310  * partitions.
311  */
312 static void
313 ck_label(struct disklabel *dl, struct cpu_disklabel *cdl)
314 {
315 	u_int			*rp, i;
316 
317 	for (i = 0; i < dl->d_npartitions; ++i) {
318 		struct partition *p = &dl->d_partitions[i];
319 		if (i == RAW_PART || p->p_size == 0)
320 			continue;
321 		if ( (p->p_offset >= cdl->cd_bslst
322 		   && p->p_offset <= cdl->cd_bslend)
323 		  || (cdl->cd_bslst >= p->p_offset
324 		   && cdl->cd_bslst <  p->p_offset + p->p_size)) {
325 			uprintf("Warning: NetBSD partition %c includes"
326 				" AHDI bad sector list\n", 'a'+i);
327 		}
328 		for (rp = &cdl->cd_roots[0]; *rp; ++rp) {
329 			if (*rp >= p->p_offset
330 			  && *rp < p->p_offset + p->p_size) {
331 				uprintf("Warning: NetBSD partition %c"
332 				" includes AHDI auxiliary root\n", 'a'+i);
333 			}
334 		}
335 	}
336 }
337 
338 /*
339  * Check volume for the existence of an AHDI label. Fetch
340  * NetBSD label from NBD or RAW partition, or otherwise
341  * create a fake NetBSD label based on the AHDI label.
342  *
343  * Returns:  0 if successful,
344  *          -1 if an I/O error occurred,
345  *          +1 if no valid AHDI label was found.
346  */
347 int
348 ahdi_label(dev_t dev, void (*strat)(struct buf *), struct disklabel *dl, struct cpu_disklabel *cdl)
349 {
350 	struct ahdi_ptbl	apt;
351 	u_int			i;
352 	int			j;
353 
354 	/*
355 	 * The AHDI format requires a specific block size.
356 	 */
357 	if (dl->d_secsize != AHDI_BSIZE)
358 		return(1);
359 
360 	/*
361 	 * Fetch the AHDI partition descriptors.
362 	 */
363 	apt.at_cdl    = cdl;
364 	apt.at_nroots = apt.at_nparts = 0;
365 	i = ahdi_getparts(dev, strat, dl->d_secpercyl,
366 			  AHDI_BBLOCK, AHDI_BBLOCK, &apt);
367 	if (i) {
368 		if (i < dl->d_secperunit)
369 			return(-1);	/* disk read error		*/
370 		else return(1);		/* reading past end of medium	*/
371 	}
372 
373 	/*
374 	 * Perform sanity checks.
375 	 */
376 	if (apt.at_bslst == 0 || apt.at_bslend == 0) {
377 		/*
378 		 * Illegal according to Atari, however some hd-utils
379 		 * use it - notably ICD *sigh*
380 		 * Work around it.....
381 		 */
382 		apt.at_bslst = apt.at_bslend = 0;
383 		uprintf("Warning: Illegal 'bad sector list' format"
384 			"- assuming non exists\n");
385 	}
386 	if (apt.at_hdsize == 0 || apt.at_nparts == 0)	/* unlikely */
387 		return(1);
388 	if (apt.at_nparts > AHDI_MAXPARTS)		/* XXX kludge */
389 		return(-1);
390 	for (i = 0; i < apt.at_nparts; ++i) {
391 		struct ahdi_part *p1 = &apt.at_parts[i];
392 
393 		for (j = 0; j < apt.at_nroots; ++j) {
394 			u_int	aux = apt.at_roots[j];
395 			if (aux >= p1->ap_st && aux <= p1->ap_end)
396 				return(1);
397 		}
398 		for (j = i + 1; j < apt.at_nparts; ++j) {
399 			struct ahdi_part *p2 = &apt.at_parts[j];
400 			if (p1->ap_st >= p2->ap_st && p1->ap_st <= p2->ap_end)
401 				return(1);
402 			if (p2->ap_st >= p1->ap_st && p2->ap_st <= p1->ap_end)
403 				return(1);
404 		}
405 		if (p1->ap_st >= apt.at_bslst && p1->ap_st <= apt.at_bslend)
406 			return(1);
407 		if (apt.at_bslst >= p1->ap_st && apt.at_bslst <= p1->ap_end)
408 			return(1);
409 	}
410 
411 	/*
412 	 * Search for a NetBSD disk label
413 	 */
414 	apt.at_bblock = NO_BOOT_BLOCK;
415 	for (i = 0; i < apt.at_nparts; ++i) {
416 		struct ahdi_part *pd = &apt.at_parts[i];
417 		u_int		 id  = *((u_int32_t *)&pd->ap_flg);
418 		if (id == AHDI_PID_NBD || id == AHDI_PID_RAW) {
419 			u_int	blkno = pd->ap_st;
420 			j = bsd_label(dev, strat, dl, blkno, &apt.at_label);
421 			if (j < 0) {
422 				return(j);		/* I/O error */
423 			}
424 			if (!j) {
425 				apt.at_bblock = blkno;	/* got it */
426 				ck_label(dl, cdl);
427 				return(0);
428 			}
429 			/*
430 			 * Not yet, but if this is the first NBD partition
431 			 * on this volume, we'll mark it anyway as a possible
432 			 * destination for future writedisklabel() calls, just
433 			 * in case there is no valid disk label on any of the
434 			 * other AHDI partitions.
435 			 */
436 			if (id == AHDI_PID_NBD
437 			    && apt.at_bblock == NO_BOOT_BLOCK)
438 				apt.at_bblock = blkno;
439 		}
440 	}
441 
442 	/*
443 	 * No NetBSD disk label on this volume, use the AHDI
444 	 * label to create a fake BSD label. If there is no
445 	 * NBD partition on this volume either, subsequent
446 	 * writedisklabel() calls will fail.
447 	 */
448 	ahdi_to_bsd(dl, &apt);
449 	return(0);
450 }
451 
452 /*
453  * Map the AHDI partition table to the NetBSD table.
454  *
455  * This means:
456  *  Part 0   : Root
457  *  Part 1   : Swap
458  *  Part 2   : Whole disk
459  *  Part 3.. : User partitions
460  *
461  * When more than one root partition is found, only the first one will
462  * be recognized as such. The others are mapped as user partitions.
463  */
464 static void
465 ahdi_to_bsd(struct disklabel *dl, struct ahdi_ptbl *apt)
466 {
467 	int		i, have_root, user_part;
468 
469 	user_part = RAW_PART;
470 	have_root = (apt->at_bblock != NO_BOOT_BLOCK);
471 
472 	for (i = 0; i < apt->at_nparts; ++i) {
473 		struct ahdi_part *pd = &apt->at_parts[i];
474 		int		 fst, pno = -1;
475 
476 		switch (*((u_int32_t *)&pd->ap_flg)) {
477 			case AHDI_PID_NBD:
478 				/*
479 				 * If this partition has been marked as the
480 				 * first NBD partition, it will be the root
481 				 * partition.
482 				 */
483 				if (pd->ap_st == apt->at_bblock)
484 					pno = 0;
485 				/* FALL THROUGH */
486 			case AHDI_PID_NBR:
487 				/*
488 				 * If there is no NBD partition and this is
489 				 * the first NBR partition, it will be the
490 				 * root partition.
491 				 */
492 				if (!have_root) {
493 					have_root = 1;
494 					pno = 0;
495 				}
496 				/* FALL THROUGH */
497 			case AHDI_PID_NBU:
498 				fst = FS_BSDFFS;
499 				break;
500 			case AHDI_PID_NBS:
501 			case AHDI_PID_SWP:
502 				if (dl->d_partitions[1].p_size == 0)
503 					pno = 1;
504 				fst = FS_SWAP;
505 				break;
506 			case AHDI_PID_BGM:
507 			case AHDI_PID_GEM:
508 				fst = FS_MSDOS;
509 				break;
510 			default:
511 				fst = FS_OTHER;
512 				break;
513 		}
514 		if (pno < 0) {
515 			if((pno = user_part + 1) >= MAXPARTITIONS)
516 				continue;
517 			user_part = pno;
518 		}
519 		dl->d_partitions[pno].p_size   = pd->ap_end - pd->ap_st + 1;
520 		dl->d_partitions[pno].p_offset = pd->ap_st;
521 		dl->d_partitions[pno].p_fstype = fst;
522 	}
523 	dl->d_npartitions = user_part + 1;
524 }
525 
526 /*
527  * Fetch the AHDI partitions and auxiliary roots.
528  *
529  * Returns:  0 if successful,
530  *           otherwise an I/O error occurred, and the
531  *           number of the offending block is returned.
532  */
533 static u_int
534 ahdi_getparts(dev, strat, secpercyl, rsec, esec, apt)
535 	dev_t			dev;
536 	void			(*strat)(struct buf *);
537 	u_int			secpercyl,
538 				rsec, esec;
539 	struct ahdi_ptbl	*apt;
540 {
541 	struct ahdi_part	*part, *end;
542 	struct ahdi_root	*root;
543 	struct buf		*bp;
544 	u_int			rv;
545 
546 	bp = geteblk(AHDI_BSIZE);
547 	bp->b_dev      = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART);
548 	bp->b_flags    |= B_READ;
549 	bp->b_bcount   = AHDI_BSIZE;
550 	bp->b_blkno    = rsec;
551 	bp->b_cylinder = rsec / secpercyl;
552 	(*strat)(bp);
553 	if (biowait(bp)) {
554 		rv = rsec + (rsec == 0);
555 		goto done;
556 	}
557 	root = (struct ahdi_root *)bp->b_data;
558 
559 	if (rsec == AHDI_BBLOCK)
560 		end = &root->ar_parts[AHDI_MAXRPD];
561 	else end = &root->ar_parts[AHDI_MAXARPD];
562 	for (part = root->ar_parts; part < end; ++part) {
563 		u_int	id = *((u_int32_t *)&part->ap_flg);
564 		if (!(id & 0x01000000))
565 			continue;
566 		if ((id &= 0x00ffffff) == AHDI_PID_XGM) {
567 			u_int	offs = part->ap_st + esec;
568 			if (apt->at_nroots < AHDI_MAXROOTS)
569 				apt->at_roots[apt->at_nroots] = offs;
570 			apt->at_nroots += 1;
571 			rv = ahdi_getparts(dev, strat, secpercyl, offs,
572 				(esec == AHDI_BBLOCK) ? offs : esec, apt);
573 			if (rv)
574 				goto done;
575 			continue;
576 		}
577 		else if (apt->at_nparts < AHDI_MAXPARTS) {
578 			struct ahdi_part *p = &apt->at_parts[apt->at_nparts];
579 			*((u_int32_t *)&p->ap_flg) = id;
580 			p->ap_st  = part->ap_st + rsec;
581 			p->ap_end = p->ap_st + part->ap_size - 1;
582 		}
583 		apt->at_nparts += 1;
584 	}
585 	apt->at_hdsize = root->ar_hdsize;
586 	apt->at_bslst  = root->ar_bslst;
587 	apt->at_bslend = root->ar_bslst + root->ar_bslsize - 1;
588 	rv = 0;
589 done:
590 	brelse(bp, 0);
591 	return(rv);
592 }
593 #endif /* DISKLABEL_AHDI */
594