1 /*	$OpenBSD: softraid_i386.c,v 1.2 2016/09/11 17:52:47 jsing Exp $	*/
2 
3 /*
4  * Copyright (c) 2012 Joel Sing <jsing@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/queue.h>
21 #include <sys/disklabel.h>
22 #include <sys/reboot.h>
23 
24 #include <dev/biovar.h>
25 #include <dev/softraidvar.h>
26 
27 #include <lib/libsa/aes_xts.h>
28 #include <lib/libsa/softraid.h>
29 
30 #include "libsa.h"
31 #include "disk.h"
32 #include "softraid_i386.h"
33 
34 void
35 srprobe_meta_opt_load(struct sr_metadata *sm, struct sr_meta_opt_head *som)
36 {
37 	struct sr_meta_opt_hdr	*omh;
38 	struct sr_meta_opt_item *omi;
39 #if 0
40 	u_int8_t checksum[MD5_DIGEST_LENGTH];
41 #endif
42 	int			i;
43 
44 	/* Process optional metadata. */
45 	omh = (struct sr_meta_opt_hdr *)((u_int8_t *)(sm + 1) +
46 	    sizeof(struct sr_meta_chunk) * sm->ssdi.ssd_chunk_no);
47 	for (i = 0; i < sm->ssdi.ssd_opt_no; i++) {
48 
49 #ifdef BIOS_DEBUG
50 		printf("Found optional metadata of type %u, length %u\n",
51 		    omh->som_type, omh->som_length);
52 #endif
53 
54 		/* Unsupported old fixed length optional metadata. */
55 		if (omh->som_length == 0) {
56 			omh = (struct sr_meta_opt_hdr *)((void *)omh +
57 			    SR_OLD_META_OPT_SIZE);
58 			continue;
59 		}
60 
61 		/* Load variable length optional metadata. */
62 		omi = alloc(sizeof(struct sr_meta_opt_item));
63 		bzero(omi, sizeof(struct sr_meta_opt_item));
64 		SLIST_INSERT_HEAD(som, omi, omi_link);
65 		omi->omi_som = alloc(omh->som_length);
66 		bzero(omi->omi_som, omh->som_length);
67 		bcopy(omh, omi->omi_som, omh->som_length);
68 
69 #if 0
70 		/* XXX - Validate checksum. */
71 		bcopy(&omi->omi_som->som_checksum, &checksum,
72 		    MD5_DIGEST_LENGTH);
73 		bzero(&omi->omi_som->som_checksum, MD5_DIGEST_LENGTH);
74 		sr_checksum(sc, omi->omi_som,
75 		    &omi->omi_som->som_checksum, omh->som_length);
76 		if (bcmp(&checksum, &omi->omi_som->som_checksum,
77 		    sizeof(checksum)))
78 			panic("%s: invalid optional metadata checksum",
79 			    DEVNAME(sc));
80 #endif
81 
82 		omh = (struct sr_meta_opt_hdr *)((void *)omh +
83 		    omh->som_length);
84 	}
85 }
86 
87 void
88 srprobe_keydisk_load(struct sr_metadata *sm)
89 {
90 	struct sr_meta_opt_hdr	*omh;
91 	struct sr_meta_keydisk	*skm;
92 	struct sr_boot_keydisk	*kd;
93 	int i;
94 
95 	/* Process optional metadata. */
96 	omh = (struct sr_meta_opt_hdr *)((u_int8_t *)(sm + 1) +
97 	    sizeof(struct sr_meta_chunk) * sm->ssdi.ssd_chunk_no);
98 	for (i = 0; i < sm->ssdi.ssd_opt_no; i++) {
99 
100 		/* Unsupported old fixed length optional metadata. */
101 		if (omh->som_length == 0) {
102 			omh = (struct sr_meta_opt_hdr *)((void *)omh +
103 			    SR_OLD_META_OPT_SIZE);
104 			continue;
105 		}
106 
107 		if (omh->som_type != SR_OPT_KEYDISK) {
108 			omh = (struct sr_meta_opt_hdr *)((void *)omh +
109 			    omh->som_length);
110 			continue;
111 		}
112 
113 		kd = alloc(sizeof(struct sr_boot_keydisk));
114 		bcopy(&sm->ssdi.ssd_uuid, &kd->kd_uuid, sizeof(kd->kd_uuid));
115 		skm = (struct sr_meta_keydisk*)omh;
116 		bcopy(&skm->skm_maskkey, &kd->kd_key, sizeof(kd->kd_key));
117 		SLIST_INSERT_HEAD(&sr_keydisks, kd, kd_link);
118 	}
119 }
120 
121 void
122 srprobe(void)
123 {
124 	struct sr_boot_volume *bv, *bv1, *bv2;
125 	struct sr_boot_chunk *bc, *bc1, *bc2;
126 	struct sr_meta_chunk *mc;
127 	struct sr_metadata *md;
128 	struct diskinfo *dip;
129 	struct partition *pp;
130 	int i, error, volno;
131 	dev_t bsd_dev;
132 	daddr_t off;
133 
134 	/* Probe for softraid volumes. */
135 	SLIST_INIT(&sr_volumes);
136 	SLIST_INIT(&sr_keydisks);
137 
138 	md = alloc(SR_META_SIZE * DEV_BSIZE);
139 
140 	TAILQ_FOREACH(dip, &disklist, list) {
141 
142 		/* Only check hard disks, skip those with I/O errors. */
143 		if ((dip->bios_info.bios_number & 0x80) == 0 ||
144 		    (dip->bios_info.flags & BDI_INVALID))
145 			continue;
146 
147 		/* Make sure disklabel has been read. */
148 		if ((dip->bios_info.flags & (BDI_BADLABEL|BDI_GOODLABEL)) == 0)
149 			continue;
150 
151 		for (i = 0; i < MAXPARTITIONS; i++) {
152 
153 			pp = &dip->disklabel.d_partitions[i];
154 			if (pp->p_fstype != FS_RAID || pp->p_size == 0)
155 				continue;
156 
157 			/* Read softraid metadata. */
158 			bzero(md, SR_META_SIZE * DEV_BSIZE);
159 			off = DL_SECTOBLK(&dip->disklabel, DL_GETPOFFSET(pp));
160 			off += SR_META_OFFSET;
161 			error = dip->diskio(F_READ, dip, off, SR_META_SIZE, md);
162 			if (error)
163 				continue;
164 
165 			/* Is this valid softraid metadata? */
166 			if (md->ssdi.ssd_magic != SR_MAGIC)
167 				continue;
168 
169 			/* XXX - validate checksum. */
170 
171 			/* Handle key disks separately... */
172 			if (md->ssdi.ssd_level == SR_KEYDISK_LEVEL) {
173 				srprobe_keydisk_load(md);
174 				continue;
175 			}
176 
177 			/* Locate chunk-specific metadata for this chunk. */
178 			mc = (struct sr_meta_chunk *)(md + 1);
179 			mc += md->ssdi.ssd_chunk_id;
180 
181 			bc = alloc(sizeof(struct sr_boot_chunk));
182 			bc->sbc_diskinfo = dip;
183 			bc->sbc_disk = dip->bios_info.bios_number;
184 			bc->sbc_part = 'a' + i;
185 
186 			bsd_dev = dip->bios_info.bsd_dev;
187 			bc->sbc_mm = MAKEBOOTDEV(B_TYPE(bsd_dev),
188 			    B_ADAPTOR(bsd_dev), B_CONTROLLER(bsd_dev),
189 			    B_UNIT(bsd_dev), bc->sbc_part - 'a');
190 
191 			bc->sbc_chunk_id = md->ssdi.ssd_chunk_id;
192 			bc->sbc_ondisk = md->ssd_ondisk;
193 			bc->sbc_state = mc->scm_status;
194 
195 			SLIST_FOREACH(bv, &sr_volumes, sbv_link) {
196 				if (bcmp(&md->ssdi.ssd_uuid, &bv->sbv_uuid,
197 				    sizeof(md->ssdi.ssd_uuid)) == 0)
198 					break;
199 			}
200 
201 			if (bv == NULL) {
202 				bv = alloc(sizeof(struct sr_boot_volume));
203 				bzero(bv, sizeof(struct sr_boot_volume));
204 				bv->sbv_level = md->ssdi.ssd_level;
205 				bv->sbv_volid = md->ssdi.ssd_volid;
206 				bv->sbv_chunk_no = md->ssdi.ssd_chunk_no;
207 				bv->sbv_flags = md->ssdi.ssd_vol_flags;
208 				bv->sbv_size = md->ssdi.ssd_size;
209 				bv->sbv_data_blkno = md->ssd_data_blkno;
210 				bcopy(&md->ssdi.ssd_uuid, &bv->sbv_uuid,
211 				    sizeof(md->ssdi.ssd_uuid));
212 				SLIST_INIT(&bv->sbv_chunks);
213 				SLIST_INIT(&bv->sbv_meta_opt);
214 
215 				/* Load optional metadata for this volume. */
216 				srprobe_meta_opt_load(md, &bv->sbv_meta_opt);
217 
218 				/* Maintain volume order. */
219 				bv2 = NULL;
220 				SLIST_FOREACH(bv1, &sr_volumes, sbv_link) {
221 					if (bv1->sbv_volid > bv->sbv_volid)
222 						break;
223 					bv2 = bv1;
224 				}
225 				if (bv2 == NULL)
226 					SLIST_INSERT_HEAD(&sr_volumes, bv,
227 					    sbv_link);
228 				else
229 					SLIST_INSERT_AFTER(bv2, bv, sbv_link);
230 			}
231 
232 			/* Maintain chunk order. */
233 			bc2 = NULL;
234 			SLIST_FOREACH(bc1, &bv->sbv_chunks, sbc_link) {
235 				if (bc1->sbc_chunk_id > bc->sbc_chunk_id)
236 					break;
237 				bc2 = bc1;
238 			}
239 			if (bc2 == NULL)
240 				SLIST_INSERT_HEAD(&bv->sbv_chunks,
241 				    bc, sbc_link);
242 			else
243 				SLIST_INSERT_AFTER(bc2, bc, sbc_link);
244 
245 			bv->sbv_chunks_found++;
246 		}
247 	}
248 
249 	/*
250 	 * Assemble RAID volumes.
251 	 */
252 	volno = 0;
253 	SLIST_FOREACH(bv, &sr_volumes, sbv_link) {
254 
255 		/* Skip if this is a hotspare "volume". */
256 		if (bv->sbv_level == SR_HOTSPARE_LEVEL &&
257 		    bv->sbv_chunk_no == 1)
258 			continue;
259 
260 		/* Determine current ondisk version. */
261 		bv->sbv_ondisk = 0;
262 		SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link) {
263 			if (bc->sbc_ondisk > bv->sbv_ondisk)
264 				bv->sbv_ondisk = bc->sbc_ondisk;
265 		}
266 		SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link) {
267 			if (bc->sbc_ondisk != bv->sbv_ondisk)
268 				bc->sbc_state = BIOC_SDOFFLINE;
269 		}
270 
271 		/* XXX - Check for duplicate chunks. */
272 
273 		/*
274 		 * Validate that volume has sufficient chunks for
275 		 * read-only access.
276 		 *
277 		 * XXX - check chunk states.
278 		 */
279 		bv->sbv_state = BIOC_SVOFFLINE;
280 		switch (bv->sbv_level) {
281 		case 0:
282 		case 'C':
283 		case 'c':
284 			if (bv->sbv_chunk_no == bv->sbv_chunks_found)
285 				bv->sbv_state = BIOC_SVONLINE;
286 			break;
287 
288 		case 1:
289 			if (bv->sbv_chunk_no == bv->sbv_chunks_found)
290 				bv->sbv_state = BIOC_SVONLINE;
291 			else if (bv->sbv_chunks_found > 0)
292 				bv->sbv_state = BIOC_SVDEGRADED;
293 			break;
294 		}
295 
296 		bv->sbv_unit = volno++;
297 		if (bv->sbv_state != BIOC_SVOFFLINE)
298 			printf(" sr%d%s", bv->sbv_unit,
299 			    bv->sbv_flags & BIOC_SCBOOTABLE ? "*" : "");
300 	}
301 
302 	explicit_bzero(md, SR_META_SIZE * DEV_BSIZE);
303 	free(md, 0);
304 }
305 
306 int
307 sr_strategy(struct sr_boot_volume *bv, int rw, daddr32_t blk, size_t size,
308     void *buf, size_t *rsize)
309 {
310 	struct diskinfo *sr_dip, *dip;
311 	struct sr_boot_chunk *bc;
312 	struct aes_xts_ctx ctx;
313 	size_t i, j, nsect;
314 	daddr_t blkno;
315 	u_char iv[8];
316 	u_char *bp;
317 	int err;
318 
319 	/* We only support read-only softraid. */
320 	if (rw != F_READ)
321 		return ENOTSUP;
322 
323 	/* Partition offset within softraid volume. */
324 	sr_dip = (struct diskinfo *)bv->sbv_diskinfo;
325 	blk += sr_dip->disklabel.d_partitions[bv->sbv_part - 'a'].p_offset;
326 
327 	if (bv->sbv_level == 0) {
328 		return ENOTSUP;
329 	} else if (bv->sbv_level == 1) {
330 
331 		/* Select first online chunk. */
332 		SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link)
333 			if (bc->sbc_state == BIOC_SDONLINE)
334 				break;
335 		if (bc == NULL)
336 			return EIO;
337 
338 		dip = (struct diskinfo *)bc->sbc_diskinfo;
339 		dip->bsddev = bc->sbc_mm;
340 		blk += bv->sbv_data_blkno;
341 
342 		/* XXX - If I/O failed we should try another chunk... */
343 		return dip->strategy(dip, rw, blk, size, buf, rsize);
344 
345 	} else if (bv->sbv_level == 'C') {
346 
347 		/* Select first online chunk. */
348 		SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link)
349 			if (bc->sbc_state == BIOC_SDONLINE)
350 				break;
351 		if (bc == NULL)
352 			return EIO;
353 
354 		dip = (struct diskinfo *)bc->sbc_diskinfo;
355 		dip->bsddev = bc->sbc_mm;
356 
357 		/* XXX - select correct key. */
358 		aes_xts_setkey(&ctx, (u_char *)bv->sbv_keys, 64);
359 
360 		nsect = (size + DEV_BSIZE - 1) / DEV_BSIZE;
361 		for (i = 0; i < nsect; i++) {
362 			blkno = blk + i;
363 			bp = ((u_char *)buf) + i * DEV_BSIZE;
364 			err = dip->strategy(dip, rw, bv->sbv_data_blkno + blkno,
365 			    DEV_BSIZE, bp, NULL);
366 			if (err != 0)
367 				return err;
368 
369 			bcopy(&blkno, iv, sizeof(blkno));
370 			aes_xts_reinit(&ctx, iv);
371 			for (j = 0; j < DEV_BSIZE; j += AES_XTS_BLOCKSIZE)
372 				aes_xts_decrypt(&ctx, bp + j);
373 		}
374 		if (rsize != NULL)
375 			*rsize = nsect * DEV_BSIZE;
376 
377 		return err;
378 
379 	} else
380 		return ENOTSUP;
381 }
382 
383 const char *
384 sr_getdisklabel(struct sr_boot_volume *bv, struct disklabel *label)
385 {
386 	struct dos_partition *dp;
387 	struct dos_mbr mbr;
388 	u_int start = 0;
389 	char buf[DEV_BSIZE];
390 	int i;
391 
392 	/* Check for MBR to determine partition offset. */
393 	bzero(&mbr, sizeof(mbr));
394 	sr_strategy(bv, F_READ, DOSBBSECTOR, sizeof(mbr), &mbr, NULL);
395 	if (mbr.dmbr_sign == DOSMBR_SIGNATURE) {
396 
397 		/* Search for OpenBSD partition */
398 		for (i = 0; i < NDOSPART; i++) {
399 			dp = &mbr.dmbr_parts[i];
400 			if (!dp->dp_size)
401 				continue;
402 			if (dp->dp_typ == DOSPTYP_OPENBSD) {
403 				start = dp->dp_start;
404 				break;
405 			}
406 		}
407 	}
408 
409 	/* Read the disklabel. */
410 	sr_strategy(bv, F_READ, start + DOS_LABELSECTOR,
411 	    sizeof(struct disklabel), buf, NULL);
412 
413 #ifdef BIOS_DEBUG
414 	printf("sr_getdisklabel: magic %lx\n",
415 	    ((struct disklabel *)buf)->d_magic);
416 	for (i = 0; i < MAXPARTITIONS; i++)
417 		printf("part %c: type = %d, size = %d, offset = %d\n", 'a' + i,
418 		    (int)((struct disklabel *)buf)->d_partitions[i].p_fstype,
419 		    (int)((struct disklabel *)buf)->d_partitions[i].p_size,
420 		    (int)((struct disklabel *)buf)->d_partitions[i].p_offset);
421 #endif
422 
423 	/* Fill in disklabel */
424 	return (getdisklabel(buf, label));
425 }
426