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 };
77 
78 /*
79  * Initialize userdisk_info structure for each disk.
80  */
81 static int
82 userdisk_init(void)
83 {
84 	off_t mediasize;
85 	u_int sectorsize;
86 	int i;
87 
88 	userdisk_maxunit = userboot_disk_maxunit;
89 	if (userdisk_maxunit > 0) {
90 		ud_info = malloc(sizeof(*ud_info) * userdisk_maxunit);
91 		if (ud_info == NULL)
92 			return (ENOMEM);
93 		for (i = 0; i < userdisk_maxunit; i++) {
94 			if (CALLBACK(diskioctl, i, DIOCGSECTORSIZE,
95 			    &sectorsize) != 0 || CALLBACK(diskioctl, i,
96 			    DIOCGMEDIASIZE, &mediasize) != 0)
97 				return (ENXIO);
98 			ud_info[i].mediasize = mediasize;
99 			ud_info[i].sectorsize = sectorsize;
100 			ud_info[i].ud_open = 0;
101 			ud_info[i].ud_bcache = NULL;
102 		}
103 	}
104 	bcache_add_dev(userdisk_maxunit);
105 	return(0);
106 }
107 
108 static void
109 userdisk_cleanup(void)
110 {
111 
112 	if (userdisk_maxunit > 0)
113 		free(ud_info);
114 }
115 
116 /*
117  * Print information about disks
118  */
119 static int
120 userdisk_print(int verbose)
121 {
122 	struct disk_devdesc dev;
123 	char line[80];
124 	int i, ret = 0;
125 
126 	if (userdisk_maxunit == 0)
127 		return (0);
128 
129 	printf("%s devices:", userboot_disk.dv_name);
130 	if ((ret = pager_output("\n")) != 0)
131 		return (ret);
132 
133 	for (i = 0; i < userdisk_maxunit; i++) {
134 		snprintf(line, sizeof(line),
135 		    "    disk%d:   Guest drive image\n", i);
136 		ret = pager_output(line);
137 		if (ret != 0)
138 			break;
139 		dev.dd.d_dev = &userboot_disk;
140 		dev.dd.d_unit = i;
141 		dev.d_slice = D_SLICENONE;
142 		dev.d_partition = D_PARTNONE;
143 		if (disk_open(&dev, ud_info[i].mediasize,
144 		    ud_info[i].sectorsize) == 0) {
145 			snprintf(line, sizeof(line), "    disk%d", i);
146 			ret = disk_print(&dev, line, verbose);
147 			disk_close(&dev);
148 			if (ret != 0)
149 				break;
150 		}
151 	}
152 	return (ret);
153 }
154 
155 /*
156  * Attempt to open the disk described by (dev) for use by (f).
157  */
158 static int
159 userdisk_open(struct open_file *f, ...)
160 {
161 	va_list			ap;
162 	struct disk_devdesc	*dev;
163 
164 	va_start(ap, f);
165 	dev = va_arg(ap, struct disk_devdesc *);
166 	va_end(ap);
167 
168 	if (dev->dd.d_unit < 0 || dev->dd.d_unit >= userdisk_maxunit)
169 		return (EIO);
170 	ud_info[dev->dd.d_unit].ud_open++;
171 	if (ud_info[dev->dd.d_unit].ud_bcache == NULL)
172 		ud_info[dev->dd.d_unit].ud_bcache = bcache_allocate();
173 	return (disk_open(dev, ud_info[dev->dd.d_unit].mediasize,
174 	    ud_info[dev->dd.d_unit].sectorsize));
175 }
176 
177 static int
178 userdisk_close(struct open_file *f)
179 {
180 	struct disk_devdesc *dev;
181 
182 	dev = (struct disk_devdesc *)f->f_devdata;
183 	ud_info[dev->dd.d_unit].ud_open--;
184 	if (ud_info[dev->dd.d_unit].ud_open == 0) {
185 		bcache_free(ud_info[dev->dd.d_unit].ud_bcache);
186 		ud_info[dev->dd.d_unit].ud_bcache = NULL;
187 	}
188 	return (disk_close(dev));
189 }
190 
191 static int
192 userdisk_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
193     char *buf, size_t *rsize)
194 {
195 	struct bcache_devdata bcd;
196 	struct disk_devdesc *dev;
197 
198 	dev = (struct disk_devdesc *)devdata;
199 	bcd.dv_strategy = userdisk_realstrategy;
200 	bcd.dv_devdata = devdata;
201 	bcd.dv_cache = ud_info[dev->dd.d_unit].ud_bcache;
202 	return (bcache_strategy(&bcd, rw, dblk + dev->d_offset,
203 	    size, buf, rsize));
204 }
205 
206 static int
207 userdisk_realstrategy(void *devdata, int rw, daddr_t dblk, size_t size,
208     char *buf, size_t *rsize)
209 {
210 	struct disk_devdesc *dev = devdata;
211 	uint64_t	off;
212 	size_t		resid;
213 	int		rc;
214 
215 	if (rsize)
216 		*rsize = 0;
217 	off = dblk * ud_info[dev->dd.d_unit].sectorsize;
218 	switch (rw & F_MASK) {
219 	case F_READ:
220 		rc = CALLBACK(diskread, dev->dd.d_unit, off, buf, size, &resid);
221 		break;
222 	case F_WRITE:
223 		rc = CALLBACK(diskwrite, dev->dd.d_unit, off, buf, size,
224 		    &resid);
225 		break;
226 	default:
227 		rc = EINVAL;
228 		break;
229 	}
230 	if (rc)
231 		return (rc);
232 	if (rsize)
233 		*rsize = size - resid;
234 	return (0);
235 }
236 
237 static int
238 userdisk_ioctl(struct open_file *f, u_long cmd, void *data)
239 {
240 	struct disk_devdesc *dev;
241 	int rc;
242 
243 	dev = (struct disk_devdesc *)f->f_devdata;
244 	rc = disk_ioctl(dev, cmd, data);
245 	if (rc != ENOTTY)
246 		return (rc);
247 
248 	return (CALLBACK(diskioctl, dev->dd.d_unit, cmd, data));
249 }
250