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