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 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 /*
31  * Userboot disk image handling.
32  */
33 
34 #include <sys/disk.h>
35 #include <stand.h>
36 #include <stdarg.h>
37 #include <bootstrap.h>
38 
39 #include "disk.h"
40 #include "libuserboot.h"
41 
42 struct userdisk_info {
43 	uint64_t	mediasize;
44 	uint16_t	sectorsize;
45 	int		ud_open;	/* reference counter */
46 	void		*ud_bcache;	/* buffer cache data */
47 };
48 
49 int userboot_disk_maxunit = 0;
50 
51 static int userdisk_maxunit = 0;
52 static struct userdisk_info	*ud_info;
53 
54 static int	userdisk_init(void);
55 static void	userdisk_cleanup(void);
56 static int	userdisk_strategy(void *devdata, int flag, daddr_t dblk,
57 		    size_t size, char *buf, size_t *rsize);
58 static int	userdisk_realstrategy(void *devdata, int flag, daddr_t dblk,
59 		    size_t size, char *buf, size_t *rsize);
60 static int	userdisk_open(struct open_file *f, ...);
61 static int	userdisk_close(struct open_file *f);
62 static int	userdisk_ioctl(struct open_file *f, u_long cmd, void *data);
63 static int	userdisk_print(int verbose);
64 
65 struct devsw userboot_disk = {
66 	.dv_name = "disk",
67 	.dv_type = DEVT_DISK,
68 	.dv_init = userdisk_init,
69 	.dv_strategy = userdisk_strategy,
70 	.dv_open = userdisk_open,
71 	.dv_close = userdisk_close,
72 	.dv_ioctl = userdisk_ioctl,
73 	.dv_print = userdisk_print,
74 	.dv_cleanup = userdisk_cleanup,
75 	.dv_fmtdev = disk_fmtdev,
76 	.dv_parsedev = disk_parsedev,
77 };
78 
79 /*
80  * Initialize userdisk_info structure for each disk.
81  */
82 static int
83 userdisk_init(void)
84 {
85 	off_t mediasize;
86 	u_int sectorsize;
87 	int i;
88 
89 	userdisk_maxunit = userboot_disk_maxunit;
90 	if (userdisk_maxunit > 0) {
91 		ud_info = malloc(sizeof(*ud_info) * userdisk_maxunit);
92 		if (ud_info == NULL)
93 			return (ENOMEM);
94 		for (i = 0; i < userdisk_maxunit; i++) {
95 			if (CALLBACK(diskioctl, i, DIOCGSECTORSIZE,
96 			    &sectorsize) != 0 || CALLBACK(diskioctl, i,
97 			    DIOCGMEDIASIZE, &mediasize) != 0)
98 				return (ENXIO);
99 			ud_info[i].mediasize = mediasize;
100 			ud_info[i].sectorsize = sectorsize;
101 			ud_info[i].ud_open = 0;
102 			ud_info[i].ud_bcache = NULL;
103 		}
104 	}
105 	bcache_add_dev(userdisk_maxunit);
106 	return(0);
107 }
108 
109 static void
110 userdisk_cleanup(void)
111 {
112 
113 	if (userdisk_maxunit > 0)
114 		free(ud_info);
115 }
116 
117 /*
118  * Print information about disks
119  */
120 static int
121 userdisk_print(int verbose)
122 {
123 	struct disk_devdesc dev;
124 	char line[80];
125 	int i, ret = 0;
126 
127 	if (userdisk_maxunit == 0)
128 		return (0);
129 
130 	printf("%s devices:", userboot_disk.dv_name);
131 	if ((ret = pager_output("\n")) != 0)
132 		return (ret);
133 
134 	for (i = 0; i < userdisk_maxunit; i++) {
135 		snprintf(line, sizeof(line),
136 		    "    disk%d:   Guest drive image\n", i);
137 		ret = pager_output(line);
138 		if (ret != 0)
139 			break;
140 		dev.dd.d_dev = &userboot_disk;
141 		dev.dd.d_unit = i;
142 		dev.d_slice = D_SLICENONE;
143 		dev.d_partition = D_PARTNONE;
144 		if (disk_open(&dev, ud_info[i].mediasize,
145 		    ud_info[i].sectorsize) == 0) {
146 			snprintf(line, sizeof(line), "    disk%d", i);
147 			ret = disk_print(&dev, line, verbose);
148 			disk_close(&dev);
149 			if (ret != 0)
150 				break;
151 		}
152 	}
153 	return (ret);
154 }
155 
156 /*
157  * Attempt to open the disk described by (dev) for use by (f).
158  */
159 static int
160 userdisk_open(struct open_file *f, ...)
161 {
162 	va_list			ap;
163 	struct disk_devdesc	*dev;
164 
165 	va_start(ap, f);
166 	dev = va_arg(ap, struct disk_devdesc *);
167 	va_end(ap);
168 
169 	if (dev->dd.d_unit < 0 || dev->dd.d_unit >= userdisk_maxunit)
170 		return (EIO);
171 	ud_info[dev->dd.d_unit].ud_open++;
172 	if (ud_info[dev->dd.d_unit].ud_bcache == NULL)
173 		ud_info[dev->dd.d_unit].ud_bcache = bcache_allocate();
174 	return (disk_open(dev, ud_info[dev->dd.d_unit].mediasize,
175 	    ud_info[dev->dd.d_unit].sectorsize));
176 }
177 
178 static int
179 userdisk_close(struct open_file *f)
180 {
181 	struct disk_devdesc *dev;
182 
183 	dev = (struct disk_devdesc *)f->f_devdata;
184 	ud_info[dev->dd.d_unit].ud_open--;
185 	if (ud_info[dev->dd.d_unit].ud_open == 0) {
186 		bcache_free(ud_info[dev->dd.d_unit].ud_bcache);
187 		ud_info[dev->dd.d_unit].ud_bcache = NULL;
188 	}
189 	return (disk_close(dev));
190 }
191 
192 static int
193 userdisk_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
194     char *buf, size_t *rsize)
195 {
196 	struct bcache_devdata bcd;
197 	struct disk_devdesc *dev;
198 
199 	dev = (struct disk_devdesc *)devdata;
200 	bcd.dv_strategy = userdisk_realstrategy;
201 	bcd.dv_devdata = devdata;
202 	bcd.dv_cache = ud_info[dev->dd.d_unit].ud_bcache;
203 	return (bcache_strategy(&bcd, rw, dblk + dev->d_offset,
204 	    size, buf, rsize));
205 }
206 
207 static int
208 userdisk_realstrategy(void *devdata, int rw, daddr_t dblk, size_t size,
209     char *buf, size_t *rsize)
210 {
211 	struct disk_devdesc *dev = devdata;
212 	uint64_t	off;
213 	size_t		resid;
214 	int		rc;
215 
216 	if (rsize)
217 		*rsize = 0;
218 	off = dblk * ud_info[dev->dd.d_unit].sectorsize;
219 	switch (rw & F_MASK) {
220 	case F_READ:
221 		rc = CALLBACK(diskread, dev->dd.d_unit, off, buf, size, &resid);
222 		break;
223 	case F_WRITE:
224 		rc = CALLBACK(diskwrite, dev->dd.d_unit, off, buf, size,
225 		    &resid);
226 		break;
227 	default:
228 		rc = EINVAL;
229 		break;
230 	}
231 	if (rc)
232 		return (rc);
233 	if (rsize)
234 		*rsize = size - resid;
235 	return (0);
236 }
237 
238 static int
239 userdisk_ioctl(struct open_file *f, u_long cmd, void *data)
240 {
241 	struct disk_devdesc *dev;
242 	int rc;
243 
244 	dev = (struct disk_devdesc *)f->f_devdata;
245 	rc = disk_ioctl(dev, cmd, data);
246 	if (rc != ENOTTY)
247 		return (rc);
248 
249 	return (CALLBACK(diskioctl, dev->dd.d_unit, cmd, data));
250 }
251