xref: /openbsd-src/sys/arch/i386/i386/dkcsum.c (revision 7f58a11f6cf13dbd32877952ad2ecc2790ae3977)
1 /*	$OpenBSD: dkcsum.c,v 1.32 2014/09/14 14:17:23 jsg Exp $	*/
2 
3 /*-
4  * Copyright (c) 1997 Niklas Hallqvist.  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 ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 /*
28  * A checksumming pseudo device used to get unique labels of each disk
29  * that needs to be matched to BIOS disks.
30  */
31 
32 #include <sys/param.h>
33 #include <sys/buf.h>
34 #include <sys/conf.h>
35 #include <sys/device.h>
36 #include <sys/disklabel.h>
37 #include <sys/fcntl.h>
38 #include <sys/reboot.h>
39 #include <sys/stat.h>
40 #include <sys/systm.h>
41 
42 #include <machine/biosvar.h>
43 
44 #include <lib/libz/zlib.h>
45 
46 dev_t dev_rawpart(struct device *);	/* XXX */
47 
48 extern u_int32_t bios_cksumlen;
49 extern bios_diskinfo_t *bios_diskinfo;
50 extern dev_t bootdev;
51 
52 void
dkcsumattach(void)53 dkcsumattach(void)
54 {
55 	struct device *dv;
56 	struct buf *bp;
57 	struct bdevsw *bdsw;
58 	dev_t dev, pribootdev, altbootdev;
59 	int error, picked;
60 	u_int32_t csum;
61 	bios_diskinfo_t *bdi, *hit;
62 
63 	/* do nothing if no diskinfo passed from /boot, or a bad length */
64 	if (bios_diskinfo == NULL || bios_cksumlen * DEV_BSIZE > MAXBSIZE)
65 		return;
66 
67 	/* Do nothing if bootdev is a CD drive. */
68 	if (B_TYPE(bootdev) == 6)
69 		return;
70 
71 #ifdef DEBUG
72 	printf("dkcsum: bootdev=%#x\n", bootdev);
73 	for (bdi = bios_diskinfo; bdi->bios_number != -1; bdi++) {
74 		if (bdi->bios_number & 0x80) {
75 			printf("dkcsum: BIOS drive %#x bsd_dev=%#x "
76 			    "checksum=%#x\n", bdi->bios_number, bdi->bsd_dev,
77 			    bdi->checksum);
78 		}
79 	}
80 #endif
81 	pribootdev = altbootdev = 0;
82 
83 	/*
84 	 * XXX What if DEV_BSIZE is changed to something else than the BIOS
85 	 * blocksize?  Today, /boot doesn't cover that case so neither need
86 	 * I care here.
87 	 */
88 	bp = geteblk(bios_cksumlen * DEV_BSIZE);	/* XXX error check?  */
89 
90 	TAILQ_FOREACH(dv, &alldevs, dv_list) {
91 		if (dv->dv_class != DV_DISK)
92 			continue;
93 		bp->b_dev = dev = dev_rawpart(dv);
94 		if (dev == NODEV)
95 			continue;
96 		bdsw = &bdevsw[major(dev)];
97 
98 		/*
99 		 * This open operation guarantees a proper initialization
100 		 * of the device, for future strategy calls.
101 		 */
102 		error = (*bdsw->d_open)(dev, FREAD, S_IFCHR, curproc);
103 		if (error) {
104 			/* XXX What to do here? */
105 #ifdef DEBUG
106 			printf("dkcsum: %s open failed (%d)\n",
107 			    dv->dv_xname, error);
108 #endif
109 			continue;
110 		}
111 
112 		/* Read blocks to cksum.  XXX maybe a d_read should be used. */
113 		bp->b_blkno = 0;
114 		bp->b_bcount = bios_cksumlen * DEV_BSIZE;
115 		bp->b_error = 0; /* B_ERROR and b_error may have stale data. */
116 		CLR(bp->b_flags, B_READ | B_WRITE | B_DONE | B_ERROR);
117 		SET(bp->b_flags, B_BUSY | B_READ | B_RAW);
118 		(*bdsw->d_strategy)(bp);
119 		if ((error = biowait(bp))) {
120 			/* XXX What to do here? */
121 #ifdef DEBUG
122 			printf("dkcsum: %s read failed (%d)\n",
123 			    dv->dv_xname, error);
124 #endif
125 			error = (*bdsw->d_close)(dev, 0, S_IFCHR, curproc);
126 #ifdef DEBUG
127 			if (error)
128 				printf("dkcsum: %s close failed (%d)\n",
129 				    dv->dv_xname, error);
130 #endif
131 			continue;
132 		}
133 		error = (*bdsw->d_close)(dev, FREAD, S_IFCHR, curproc);
134 		if (error) {
135 			/* XXX What to do here? */
136 #ifdef DEBUG
137 			printf("dkcsum: %s closed failed (%d)\n",
138 			    dv->dv_xname, error);
139 #endif
140 			continue;
141 		}
142 
143 		csum = adler32(0, bp->b_data, bios_cksumlen * DEV_BSIZE);
144 #ifdef DEBUG
145 		printf("dkcsum: %s checksum is %#x\n", dv->dv_xname, csum);
146 #endif
147 
148 		/* Find the BIOS device */
149 		hit = 0;
150 		for (bdi = bios_diskinfo; bdi->bios_number != -1; bdi++) {
151 			/* Skip non-harddrives */
152 			if (!(bdi->bios_number & 0x80))
153 				continue;
154 			if (bdi->checksum != csum)
155 				continue;
156 			picked = hit || (bdi->flags & BDI_PICKED);
157 			if (!picked)
158 				hit = bdi;
159 #ifdef DEBUG
160 			printf("dkcsum: %s matches BIOS drive %#x%s\n",
161 			    dv->dv_xname, bdi->bios_number,
162 			    (picked ? " IGNORED" : ""));
163 #endif
164 		}
165 
166 		/*
167 		 * If we have no hit, that's OK, we can see a lot more devices
168 		 * than the BIOS can, so this case is pretty normal.
169 		 */
170 		if (!hit) {
171 #ifdef DEBUG
172 			printf("dkcsum: %s has no matching BIOS drive\n",
173 			    dv->dv_xname);
174 #endif
175 			continue;
176 		}
177 
178 		/*
179 		 * Fixup bootdev if units match.  This means that all of
180 		 * hd*, sd*, wd*, will be interpreted the same.  Not 100%
181 		 * backwards compatible, but sd* and wd* should be phased-
182 		 * out in the bootblocks.
183 		 */
184 
185 		/* B_TYPE dependent hd unit counting bootblocks */
186 		if ((B_ADAPTOR(bootdev) == B_ADAPTOR(hit->bsd_dev)) &&
187 		    (B_CONTROLLER(bootdev) == B_CONTROLLER(hit->bsd_dev)) &&
188 		    (B_TYPE(bootdev) == B_TYPE(hit->bsd_dev)) &&
189 		    (B_UNIT(bootdev) == B_UNIT(hit->bsd_dev))) {
190 			int type, ctrl, adap, part, unit;
191 
192 			type = major(bp->b_dev);
193 			adap = B_ADAPTOR(bootdev);
194 			ctrl = B_CONTROLLER(bootdev);
195 			unit = DISKUNIT(bp->b_dev);
196 			part = B_PARTITION(bootdev);
197 
198 			pribootdev = MAKEBOOTDEV(type, ctrl, adap, unit, part);
199 #ifdef DEBUG
200 			printf("dkcsum: %s is primary boot disk\n",
201 			    dv->dv_xname);
202 #endif
203 		}
204 		/* B_TYPE independent hd unit counting bootblocks */
205 		if (B_UNIT(bootdev) == (hit->bios_number & 0x7F)) {
206 			int type, ctrl, adap, part, unit;
207 
208 			type = major(bp->b_dev);
209 			adap = B_ADAPTOR(bootdev);
210 			ctrl = B_CONTROLLER(bootdev);
211 			unit = DISKUNIT(bp->b_dev);
212 			part = B_PARTITION(bootdev);
213 
214 			altbootdev = MAKEBOOTDEV(type, ctrl, adap, unit, part);
215 #ifdef DEBUG
216 			printf("dkcsum: %s is alternate boot disk\n",
217 			    dv->dv_xname);
218 #endif
219 		}
220 
221 		/* This will overwrite /boot's guess, just so you remember */
222 		hit->bsd_dev = MAKEBOOTDEV(major(bp->b_dev), 0, 0,
223 		    DISKUNIT(bp->b_dev), RAW_PART);
224 		hit->flags |= BDI_PICKED;
225 	}
226 	bootdev = pribootdev ? pribootdev : altbootdev ? altbootdev : bootdev;
227 
228 	bp->b_flags |= B_INVAL;
229 	brelse(bp);
230 }
231