1 /*-
2  * Copyright (c) 2011 Google, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 /*
28  * Userboot disk image handling.
29  */
30 
31 #include <sys/disk.h>
32 #include <stand.h>
33 #include <stdarg.h>
34 #include <bootstrap.h>
35 
36 #include "disk.h"
37 #include "libuserboot.h"
38 
39 struct userdisk_info {
40 	uint64_t	mediasize;
41 	uint16_t	sectorsize;
42 	int		ud_open;	/* reference counter */
43 	void		*ud_bcache;	/* buffer cache data */
44 };
45 
46 int userboot_disk_maxunit = 0;
47 
48 static int userdisk_maxunit = 0;
49 static struct userdisk_info	*ud_info;
50 
51 static int	userdisk_init(void);
52 static void	userdisk_cleanup(void);
53 static int	userdisk_strategy(void *devdata, int flag, daddr_t dblk,
54 		    size_t size, char *buf, size_t *rsize);
55 static int	userdisk_realstrategy(void *devdata, int flag, daddr_t dblk,
56 		    size_t size, char *buf, size_t *rsize);
57 static int	userdisk_open(struct open_file *f, ...);
58 static int	userdisk_close(struct open_file *f);
59 static int	userdisk_ioctl(struct open_file *f, u_long cmd, void *data);
60 static int	userdisk_print(int verbose);
61 
62 struct devsw userboot_disk = {
63 	.dv_name = "disk",
64 	.dv_type = DEVT_DISK,
65 	.dv_init = userdisk_init,
66 	.dv_strategy = userdisk_strategy,
67 	.dv_open = userdisk_open,
68 	.dv_close = userdisk_close,
69 	.dv_ioctl = userdisk_ioctl,
70 	.dv_print = userdisk_print,
71 	.dv_cleanup = userdisk_cleanup,
72 	.dv_fmtdev = disk_fmtdev,
73 	.dv_parsedev = disk_parsedev,
74 };
75 
76 /*
77  * Initialize userdisk_info structure for each disk.
78  */
79 static int
80 userdisk_init(void)
81 {
82 	off_t mediasize;
83 	u_int sectorsize;
84 	int i;
85 
86 	userdisk_maxunit = userboot_disk_maxunit;
87 	if (userdisk_maxunit > 0) {
88 		ud_info = malloc(sizeof(*ud_info) * userdisk_maxunit);
89 		if (ud_info == NULL)
90 			return (ENOMEM);
91 		for (i = 0; i < userdisk_maxunit; i++) {
92 			if (CALLBACK(diskioctl, i, DIOCGSECTORSIZE,
93 			    &sectorsize) != 0 || CALLBACK(diskioctl, i,
94 			    DIOCGMEDIASIZE, &mediasize) != 0)
95 				return (ENXIO);
96 			ud_info[i].mediasize = mediasize;
97 			ud_info[i].sectorsize = sectorsize;
98 			ud_info[i].ud_open = 0;
99 			ud_info[i].ud_bcache = NULL;
100 		}
101 	}
102 	bcache_add_dev(userdisk_maxunit);
103 	return(0);
104 }
105 
106 static void
107 userdisk_cleanup(void)
108 {
109 
110 	if (userdisk_maxunit > 0)
111 		free(ud_info);
112 }
113 
114 /*
115  * Print information about disks
116  */
117 static int
118 userdisk_print(int verbose)
119 {
120 	struct disk_devdesc dev;
121 	char line[80];
122 	int i, ret = 0;
123 
124 	if (userdisk_maxunit == 0)
125 		return (0);
126 
127 	printf("%s devices:", userboot_disk.dv_name);
128 	if ((ret = pager_output("\n")) != 0)
129 		return (ret);
130 
131 	for (i = 0; i < userdisk_maxunit; i++) {
132 		snprintf(line, sizeof(line),
133 		    "    disk%d:   Guest drive image\n", i);
134 		ret = pager_output(line);
135 		if (ret != 0)
136 			break;
137 		dev.dd.d_dev = &userboot_disk;
138 		dev.dd.d_unit = i;
139 		dev.d_slice = D_SLICENONE;
140 		dev.d_partition = D_PARTNONE;
141 		if (disk_open(&dev, ud_info[i].mediasize,
142 		    ud_info[i].sectorsize) == 0) {
143 			snprintf(line, sizeof(line), "    disk%d", i);
144 			ret = disk_print(&dev, line, verbose);
145 			disk_close(&dev);
146 			if (ret != 0)
147 				break;
148 		}
149 	}
150 	return (ret);
151 }
152 
153 /*
154  * Attempt to open the disk described by (dev) for use by (f).
155  */
156 static int
157 userdisk_open(struct open_file *f, ...)
158 {
159 	va_list			ap;
160 	struct disk_devdesc	*dev;
161 
162 	va_start(ap, f);
163 	dev = va_arg(ap, struct disk_devdesc *);
164 	va_end(ap);
165 
166 	if (dev->dd.d_unit < 0 || dev->dd.d_unit >= userdisk_maxunit)
167 		return (EIO);
168 	ud_info[dev->dd.d_unit].ud_open++;
169 	if (ud_info[dev->dd.d_unit].ud_bcache == NULL)
170 		ud_info[dev->dd.d_unit].ud_bcache = bcache_allocate();
171 	return (disk_open(dev, ud_info[dev->dd.d_unit].mediasize,
172 	    ud_info[dev->dd.d_unit].sectorsize));
173 }
174 
175 static int
176 userdisk_close(struct open_file *f)
177 {
178 	struct disk_devdesc *dev;
179 
180 	dev = (struct disk_devdesc *)f->f_devdata;
181 	ud_info[dev->dd.d_unit].ud_open--;
182 	if (ud_info[dev->dd.d_unit].ud_open == 0) {
183 		bcache_free(ud_info[dev->dd.d_unit].ud_bcache);
184 		ud_info[dev->dd.d_unit].ud_bcache = NULL;
185 	}
186 	return (disk_close(dev));
187 }
188 
189 static int
190 userdisk_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
191     char *buf, size_t *rsize)
192 {
193 	struct bcache_devdata bcd;
194 	struct disk_devdesc *dev;
195 
196 	dev = (struct disk_devdesc *)devdata;
197 	bcd.dv_strategy = userdisk_realstrategy;
198 	bcd.dv_devdata = devdata;
199 	bcd.dv_cache = ud_info[dev->dd.d_unit].ud_bcache;
200 	return (bcache_strategy(&bcd, rw, dblk + dev->d_offset,
201 	    size, buf, rsize));
202 }
203 
204 static int
205 userdisk_realstrategy(void *devdata, int rw, daddr_t dblk, size_t size,
206     char *buf, size_t *rsize)
207 {
208 	struct disk_devdesc *dev = devdata;
209 	uint64_t	off;
210 	size_t		resid;
211 	int		rc;
212 
213 	if (rsize)
214 		*rsize = 0;
215 	off = dblk * ud_info[dev->dd.d_unit].sectorsize;
216 	switch (rw & F_MASK) {
217 	case F_READ:
218 		rc = CALLBACK(diskread, dev->dd.d_unit, off, buf, size, &resid);
219 		break;
220 	case F_WRITE:
221 		rc = CALLBACK(diskwrite, dev->dd.d_unit, off, buf, size,
222 		    &resid);
223 		break;
224 	default:
225 		rc = EINVAL;
226 		break;
227 	}
228 	if (rc)
229 		return (rc);
230 	if (rsize)
231 		*rsize = size - resid;
232 	return (0);
233 }
234 
235 static int
236 userdisk_ioctl(struct open_file *f, u_long cmd, void *data)
237 {
238 	struct disk_devdesc *dev;
239 	int rc;
240 
241 	dev = (struct disk_devdesc *)f->f_devdata;
242 	rc = disk_ioctl(dev, cmd, data);
243 	if (rc != ENOTTY)
244 		return (rc);
245 
246 	return (CALLBACK(diskioctl, dev->dd.d_unit, cmd, data));
247 }
248