1227652Sgrehan/*-
2253132Sbryanv * Copyright (c) 2011, Bryan Venteicher <bryanv@FreeBSD.org>
3227652Sgrehan * All rights reserved.
4227652Sgrehan *
5227652Sgrehan * Redistribution and use in source and binary forms, with or without
6227652Sgrehan * modification, are permitted provided that the following conditions
7227652Sgrehan * are met:
8227652Sgrehan * 1. Redistributions of source code must retain the above copyright
9227652Sgrehan *    notice unmodified, this list of conditions, and the following
10227652Sgrehan *    disclaimer.
11227652Sgrehan * 2. Redistributions in binary form must reproduce the above copyright
12227652Sgrehan *    notice, this list of conditions and the following disclaimer in the
13227652Sgrehan *    documentation and/or other materials provided with the distribution.
14227652Sgrehan *
15227652Sgrehan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16227652Sgrehan * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17227652Sgrehan * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18227652Sgrehan * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19227652Sgrehan * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20227652Sgrehan * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21227652Sgrehan * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22227652Sgrehan * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23227652Sgrehan * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24227652Sgrehan * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25227652Sgrehan */
26227652Sgrehan
27227652Sgrehan/* Driver for VirtIO block devices. */
28227652Sgrehan
29227652Sgrehan#include <sys/cdefs.h>
30227652Sgrehan__FBSDID("$FreeBSD$");
31227652Sgrehan
32227652Sgrehan#include <sys/param.h>
33227652Sgrehan#include <sys/systm.h>
34227652Sgrehan#include <sys/kernel.h>
35227652Sgrehan#include <sys/bio.h>
36227652Sgrehan#include <sys/malloc.h>
37227652Sgrehan#include <sys/module.h>
38227652Sgrehan#include <sys/sglist.h>
39253132Sbryanv#include <sys/sysctl.h>
40227652Sgrehan#include <sys/lock.h>
41227652Sgrehan#include <sys/mutex.h>
42227652Sgrehan#include <sys/queue.h>
43227652Sgrehan
44227652Sgrehan#include <geom/geom_disk.h>
45227652Sgrehan
46227652Sgrehan#include <machine/bus.h>
47227652Sgrehan#include <machine/resource.h>
48227652Sgrehan#include <sys/bus.h>
49227652Sgrehan#include <sys/rman.h>
50227652Sgrehan
51227652Sgrehan#include <dev/virtio/virtio.h>
52227652Sgrehan#include <dev/virtio/virtqueue.h>
53227652Sgrehan#include <dev/virtio/block/virtio_blk.h>
54227652Sgrehan
55227652Sgrehan#include "virtio_if.h"
56227652Sgrehan
57227652Sgrehanstruct vtblk_request {
58227652Sgrehan	struct virtio_blk_outhdr	 vbr_hdr;
59227652Sgrehan	struct bio			*vbr_bp;
60227652Sgrehan	uint8_t				 vbr_ack;
61227652Sgrehan
62227652Sgrehan	TAILQ_ENTRY(vtblk_request)	 vbr_link;
63227652Sgrehan};
64227652Sgrehan
65253132Sbryanvenum vtblk_cache_mode {
66253132Sbryanv	VTBLK_CACHE_WRITETHROUGH,
67253132Sbryanv	VTBLK_CACHE_WRITEBACK,
68253132Sbryanv	VTBLK_CACHE_MAX
69253132Sbryanv};
70253132Sbryanv
71227652Sgrehanstruct vtblk_softc {
72227652Sgrehan	device_t		 vtblk_dev;
73227652Sgrehan	struct mtx		 vtblk_mtx;
74227652Sgrehan	uint64_t		 vtblk_features;
75227652Sgrehan	uint32_t		 vtblk_flags;
76227652Sgrehan#define VTBLK_FLAG_INDIRECT	0x0001
77227652Sgrehan#define VTBLK_FLAG_READONLY	0x0002
78239472Semaste#define VTBLK_FLAG_DETACH	0x0004
79239472Semaste#define VTBLK_FLAG_SUSPEND	0x0008
80227652Sgrehan#define VTBLK_FLAG_DUMPING	0x0010
81248026Sbryanv#define VTBLK_FLAG_BARRIER	0x0020
82253132Sbryanv#define VTBLK_FLAG_WC_CONFIG	0x0040
83227652Sgrehan
84227652Sgrehan	struct virtqueue	*vtblk_vq;
85227652Sgrehan	struct sglist		*vtblk_sglist;
86227652Sgrehan	struct disk		*vtblk_disk;
87227652Sgrehan
88227652Sgrehan	struct bio_queue_head	 vtblk_bioq;
89227652Sgrehan	TAILQ_HEAD(, vtblk_request)
90227652Sgrehan				 vtblk_req_free;
91227652Sgrehan	TAILQ_HEAD(, vtblk_request)
92248026Sbryanv				 vtblk_req_ready;
93248026Sbryanv	struct vtblk_request	*vtblk_req_ordered;
94227652Sgrehan
95227652Sgrehan	int			 vtblk_max_nsegs;
96227652Sgrehan	int			 vtblk_request_count;
97253132Sbryanv	enum vtblk_cache_mode	 vtblk_write_cache;
98227652Sgrehan
99227652Sgrehan	struct vtblk_request	 vtblk_dump_request;
100227652Sgrehan};
101227652Sgrehan
102227652Sgrehanstatic struct virtio_feature_desc vtblk_feature_desc[] = {
103227652Sgrehan	{ VIRTIO_BLK_F_BARRIER,		"HostBarrier"	},
104227652Sgrehan	{ VIRTIO_BLK_F_SIZE_MAX,	"MaxSegSize"	},
105227652Sgrehan	{ VIRTIO_BLK_F_SEG_MAX,		"MaxNumSegs"	},
106227652Sgrehan	{ VIRTIO_BLK_F_GEOMETRY,	"DiskGeometry"	},
107227652Sgrehan	{ VIRTIO_BLK_F_RO,		"ReadOnly"	},
108227652Sgrehan	{ VIRTIO_BLK_F_BLK_SIZE,	"BlockSize"	},
109227652Sgrehan	{ VIRTIO_BLK_F_SCSI,		"SCSICmds"	},
110253132Sbryanv	{ VIRTIO_BLK_F_WCE,		"WriteCache"	},
111227652Sgrehan	{ VIRTIO_BLK_F_TOPOLOGY,	"Topology"	},
112253132Sbryanv	{ VIRTIO_BLK_F_CONFIG_WCE,	"ConfigWCE"	},
113227652Sgrehan
114227652Sgrehan	{ 0, NULL }
115227652Sgrehan};
116227652Sgrehan
117227652Sgrehanstatic int	vtblk_modevent(module_t, int, void *);
118227652Sgrehan
119227652Sgrehanstatic int	vtblk_probe(device_t);
120227652Sgrehanstatic int	vtblk_attach(device_t);
121227652Sgrehanstatic int	vtblk_detach(device_t);
122227652Sgrehanstatic int	vtblk_suspend(device_t);
123227652Sgrehanstatic int	vtblk_resume(device_t);
124227652Sgrehanstatic int	vtblk_shutdown(device_t);
125253132Sbryanvstatic int	vtblk_config_change(device_t);
126227652Sgrehan
127239472Semastestatic int	vtblk_open(struct disk *);
128239472Semastestatic int	vtblk_close(struct disk *);
129239472Semastestatic int	vtblk_ioctl(struct disk *, u_long, void *, int,
130246582Sbryanv		    struct thread *);
131239472Semastestatic int	vtblk_dump(void *, void *, vm_offset_t, off_t, size_t);
132239472Semastestatic void	vtblk_strategy(struct bio *);
133239472Semaste
134227652Sgrehanstatic void	vtblk_negotiate_features(struct vtblk_softc *);
135227652Sgrehanstatic int	vtblk_maximum_segments(struct vtblk_softc *,
136227652Sgrehan		    struct virtio_blk_config *);
137227652Sgrehanstatic int	vtblk_alloc_virtqueue(struct vtblk_softc *);
138253132Sbryanvstatic void	vtblk_set_write_cache(struct vtblk_softc *, int);
139253132Sbryanvstatic int	vtblk_write_cache_enabled(struct vtblk_softc *sc,
140253132Sbryanv		    struct virtio_blk_config *);
141253132Sbryanvstatic int	vtblk_write_cache_sysctl(SYSCTL_HANDLER_ARGS);
142227652Sgrehanstatic void	vtblk_alloc_disk(struct vtblk_softc *,
143227652Sgrehan		    struct virtio_blk_config *);
144227652Sgrehanstatic void	vtblk_create_disk(struct vtblk_softc *);
145227652Sgrehan
146239472Semastestatic int	vtblk_quiesce(struct vtblk_softc *);
147227652Sgrehanstatic void	vtblk_startio(struct vtblk_softc *);
148227652Sgrehanstatic struct vtblk_request * vtblk_bio_request(struct vtblk_softc *);
149227652Sgrehanstatic int	vtblk_execute_request(struct vtblk_softc *,
150227652Sgrehan		    struct vtblk_request *);
151227652Sgrehan
152253132Sbryanvstatic void	vtblk_vq_intr(void *);
153227652Sgrehan
154227652Sgrehanstatic void	vtblk_stop(struct vtblk_softc *);
155227652Sgrehan
156253132Sbryanvstatic void	vtblk_read_config(struct vtblk_softc *,
157253132Sbryanv		    struct virtio_blk_config *);
158227652Sgrehanstatic void	vtblk_get_ident(struct vtblk_softc *);
159227652Sgrehanstatic void	vtblk_prepare_dump(struct vtblk_softc *);
160227652Sgrehanstatic int	vtblk_write_dump(struct vtblk_softc *, void *, off_t, size_t);
161227652Sgrehanstatic int	vtblk_flush_dump(struct vtblk_softc *);
162227652Sgrehanstatic int	vtblk_poll_request(struct vtblk_softc *,
163227652Sgrehan		    struct vtblk_request *);
164227652Sgrehan
165239472Semastestatic void	vtblk_finish_completed(struct vtblk_softc *);
166227652Sgrehanstatic void	vtblk_drain_vq(struct vtblk_softc *, int);
167227652Sgrehanstatic void	vtblk_drain(struct vtblk_softc *);
168227652Sgrehan
169227652Sgrehanstatic int	vtblk_alloc_requests(struct vtblk_softc *);
170227652Sgrehanstatic void	vtblk_free_requests(struct vtblk_softc *);
171227652Sgrehanstatic struct vtblk_request * vtblk_dequeue_request(struct vtblk_softc *);
172227652Sgrehanstatic void	vtblk_enqueue_request(struct vtblk_softc *,
173227652Sgrehan		    struct vtblk_request *);
174227652Sgrehan
175227652Sgrehanstatic struct vtblk_request * vtblk_dequeue_ready(struct vtblk_softc *);
176227652Sgrehanstatic void	vtblk_enqueue_ready(struct vtblk_softc *,
177227652Sgrehan		    struct vtblk_request *);
178227652Sgrehan
179239472Semastestatic int	vtblk_request_error(struct vtblk_request *);
180239472Semastestatic void	vtblk_finish_bio(struct bio *, int);
181227652Sgrehan
182253132Sbryanvstatic void	vtblk_setup_sysctl(struct vtblk_softc *);
183253132Sbryanvstatic int	vtblk_tunable_int(struct vtblk_softc *, const char *, int);
184253132Sbryanv
185227652Sgrehan/* Tunables. */
186227652Sgrehanstatic int vtblk_no_ident = 0;
187227652SgrehanTUNABLE_INT("hw.vtblk.no_ident", &vtblk_no_ident);
188253132Sbryanvstatic int vtblk_writecache_mode = -1;
189253132SbryanvTUNABLE_INT("hw.vtblk.writecache_mode", &vtblk_writecache_mode);
190227652Sgrehan
191227652Sgrehan/* Features desired/implemented by this driver. */
192227652Sgrehan#define VTBLK_FEATURES \
193227652Sgrehan    (VIRTIO_BLK_F_BARRIER		| \
194227652Sgrehan     VIRTIO_BLK_F_SIZE_MAX		| \
195227652Sgrehan     VIRTIO_BLK_F_SEG_MAX		| \
196227652Sgrehan     VIRTIO_BLK_F_GEOMETRY		| \
197227652Sgrehan     VIRTIO_BLK_F_RO			| \
198227652Sgrehan     VIRTIO_BLK_F_BLK_SIZE		| \
199253132Sbryanv     VIRTIO_BLK_F_WCE			| \
200253132Sbryanv     VIRTIO_BLK_F_CONFIG_WCE		| \
201227652Sgrehan     VIRTIO_RING_F_INDIRECT_DESC)
202227652Sgrehan
203227652Sgrehan#define VTBLK_MTX(_sc)		&(_sc)->vtblk_mtx
204227652Sgrehan#define VTBLK_LOCK_INIT(_sc, _name) \
205227652Sgrehan				mtx_init(VTBLK_MTX((_sc)), (_name), \
206253132Sbryanv				    "VirtIO Block Lock", MTX_DEF)
207227652Sgrehan#define VTBLK_LOCK(_sc)		mtx_lock(VTBLK_MTX((_sc)))
208227652Sgrehan#define VTBLK_UNLOCK(_sc)	mtx_unlock(VTBLK_MTX((_sc)))
209227652Sgrehan#define VTBLK_LOCK_DESTROY(_sc)	mtx_destroy(VTBLK_MTX((_sc)))
210227652Sgrehan#define VTBLK_LOCK_ASSERT(_sc)	mtx_assert(VTBLK_MTX((_sc)), MA_OWNED)
211227652Sgrehan#define VTBLK_LOCK_ASSERT_NOTOWNED(_sc) \
212227652Sgrehan				mtx_assert(VTBLK_MTX((_sc)), MA_NOTOWNED)
213227652Sgrehan
214227652Sgrehan#define VTBLK_DISK_NAME		"vtbd"
215246582Sbryanv#define VTBLK_QUIESCE_TIMEOUT	(30 * hz)
216227652Sgrehan
217227652Sgrehan/*
218227652Sgrehan * Each block request uses at least two segments - one for the header
219227652Sgrehan * and one for the status.
220227652Sgrehan */
221227652Sgrehan#define VTBLK_MIN_SEGMENTS	2
222227652Sgrehan
223227652Sgrehanstatic device_method_t vtblk_methods[] = {
224227652Sgrehan	/* Device methods. */
225227652Sgrehan	DEVMETHOD(device_probe,		vtblk_probe),
226227652Sgrehan	DEVMETHOD(device_attach,	vtblk_attach),
227227652Sgrehan	DEVMETHOD(device_detach,	vtblk_detach),
228227652Sgrehan	DEVMETHOD(device_suspend,	vtblk_suspend),
229227652Sgrehan	DEVMETHOD(device_resume,	vtblk_resume),
230227652Sgrehan	DEVMETHOD(device_shutdown,	vtblk_shutdown),
231227652Sgrehan
232253132Sbryanv	/* VirtIO methods. */
233253132Sbryanv	DEVMETHOD(virtio_config_change,	vtblk_config_change),
234253132Sbryanv
235239472Semaste	DEVMETHOD_END
236227652Sgrehan};
237227652Sgrehan
238227652Sgrehanstatic driver_t vtblk_driver = {
239227652Sgrehan	"vtblk",
240227652Sgrehan	vtblk_methods,
241227652Sgrehan	sizeof(struct vtblk_softc)
242227652Sgrehan};
243227652Sgrehanstatic devclass_t vtblk_devclass;
244227652Sgrehan
245227652SgrehanDRIVER_MODULE(virtio_blk, virtio_pci, vtblk_driver, vtblk_devclass,
246227652Sgrehan    vtblk_modevent, 0);
247227652SgrehanMODULE_VERSION(virtio_blk, 1);
248227652SgrehanMODULE_DEPEND(virtio_blk, virtio, 1, 1, 1);
249227652Sgrehan
250227652Sgrehanstatic int
251227652Sgrehanvtblk_modevent(module_t mod, int type, void *unused)
252227652Sgrehan{
253227652Sgrehan	int error;
254227652Sgrehan
255227652Sgrehan	error = 0;
256227652Sgrehan
257227652Sgrehan	switch (type) {
258227652Sgrehan	case MOD_LOAD:
259227652Sgrehan	case MOD_QUIESCE:
260227652Sgrehan	case MOD_UNLOAD:
261227652Sgrehan	case MOD_SHUTDOWN:
262227652Sgrehan		break;
263227652Sgrehan	default:
264227652Sgrehan		error = EOPNOTSUPP;
265227652Sgrehan		break;
266227652Sgrehan	}
267227652Sgrehan
268227652Sgrehan	return (error);
269227652Sgrehan}
270227652Sgrehan
271227652Sgrehanstatic int
272227652Sgrehanvtblk_probe(device_t dev)
273227652Sgrehan{
274227652Sgrehan
275227652Sgrehan	if (virtio_get_device_type(dev) != VIRTIO_ID_BLOCK)
276227652Sgrehan		return (ENXIO);
277227652Sgrehan
278227652Sgrehan	device_set_desc(dev, "VirtIO Block Adapter");
279227652Sgrehan
280227652Sgrehan	return (BUS_PROBE_DEFAULT);
281227652Sgrehan}
282227652Sgrehan
283227652Sgrehanstatic int
284227652Sgrehanvtblk_attach(device_t dev)
285227652Sgrehan{
286227652Sgrehan	struct vtblk_softc *sc;
287227652Sgrehan	struct virtio_blk_config blkcfg;
288227652Sgrehan	int error;
289227652Sgrehan
290227652Sgrehan	sc = device_get_softc(dev);
291227652Sgrehan	sc->vtblk_dev = dev;
292227652Sgrehan
293227652Sgrehan	VTBLK_LOCK_INIT(sc, device_get_nameunit(dev));
294227652Sgrehan
295227652Sgrehan	bioq_init(&sc->vtblk_bioq);
296227652Sgrehan	TAILQ_INIT(&sc->vtblk_req_free);
297227652Sgrehan	TAILQ_INIT(&sc->vtblk_req_ready);
298227652Sgrehan
299227652Sgrehan	virtio_set_feature_desc(dev, vtblk_feature_desc);
300227652Sgrehan	vtblk_negotiate_features(sc);
301227652Sgrehan
302227652Sgrehan	if (virtio_with_feature(dev, VIRTIO_RING_F_INDIRECT_DESC))
303227652Sgrehan		sc->vtblk_flags |= VTBLK_FLAG_INDIRECT;
304227652Sgrehan	if (virtio_with_feature(dev, VIRTIO_BLK_F_RO))
305227652Sgrehan		sc->vtblk_flags |= VTBLK_FLAG_READONLY;
306248026Sbryanv	if (virtio_with_feature(dev, VIRTIO_BLK_F_BARRIER))
307248026Sbryanv		sc->vtblk_flags |= VTBLK_FLAG_BARRIER;
308253132Sbryanv	if (virtio_with_feature(dev, VIRTIO_BLK_F_CONFIG_WCE))
309253132Sbryanv		sc->vtblk_flags |= VTBLK_FLAG_WC_CONFIG;
310227652Sgrehan
311253132Sbryanv	vtblk_setup_sysctl(sc);
312253132Sbryanv
313227652Sgrehan	/* Get local copy of config. */
314253132Sbryanv	vtblk_read_config(sc, &blkcfg);
315227652Sgrehan
316227652Sgrehan	/*
317227652Sgrehan	 * With the current sglist(9) implementation, it is not easy
318227652Sgrehan	 * for us to support a maximum segment size as adjacent
319227652Sgrehan	 * segments are coalesced. For now, just make sure it's larger
320227652Sgrehan	 * than the maximum supported transfer size.
321227652Sgrehan	 */
322227652Sgrehan	if (virtio_with_feature(dev, VIRTIO_BLK_F_SIZE_MAX)) {
323227652Sgrehan		if (blkcfg.size_max < MAXPHYS) {
324227652Sgrehan			error = ENOTSUP;
325227652Sgrehan			device_printf(dev, "host requires unsupported "
326227652Sgrehan			    "maximum segment size feature\n");
327227652Sgrehan			goto fail;
328227652Sgrehan		}
329227652Sgrehan	}
330227652Sgrehan
331227652Sgrehan	sc->vtblk_max_nsegs = vtblk_maximum_segments(sc, &blkcfg);
332246582Sbryanv	if (sc->vtblk_max_nsegs <= VTBLK_MIN_SEGMENTS) {
333239472Semaste		error = EINVAL;
334239472Semaste		device_printf(dev, "fewer than minimum number of segments "
335239472Semaste		    "allowed: %d\n", sc->vtblk_max_nsegs);
336239472Semaste		goto fail;
337239472Semaste	}
338227652Sgrehan
339227652Sgrehan	sc->vtblk_sglist = sglist_alloc(sc->vtblk_max_nsegs, M_NOWAIT);
340227652Sgrehan	if (sc->vtblk_sglist == NULL) {
341227652Sgrehan		error = ENOMEM;
342227652Sgrehan		device_printf(dev, "cannot allocate sglist\n");
343227652Sgrehan		goto fail;
344227652Sgrehan	}
345227652Sgrehan
346227652Sgrehan	error = vtblk_alloc_virtqueue(sc);
347227652Sgrehan	if (error) {
348227652Sgrehan		device_printf(dev, "cannot allocate virtqueue\n");
349227652Sgrehan		goto fail;
350227652Sgrehan	}
351227652Sgrehan
352227652Sgrehan	error = vtblk_alloc_requests(sc);
353227652Sgrehan	if (error) {
354227652Sgrehan		device_printf(dev, "cannot preallocate requests\n");
355227652Sgrehan		goto fail;
356227652Sgrehan	}
357227652Sgrehan
358227652Sgrehan	vtblk_alloc_disk(sc, &blkcfg);
359227652Sgrehan
360227652Sgrehan	error = virtio_setup_intr(dev, INTR_TYPE_BIO | INTR_ENTROPY);
361227652Sgrehan	if (error) {
362227652Sgrehan		device_printf(dev, "cannot setup virtqueue interrupt\n");
363227652Sgrehan		goto fail;
364227652Sgrehan	}
365227652Sgrehan
366227652Sgrehan	vtblk_create_disk(sc);
367227652Sgrehan
368227652Sgrehan	virtqueue_enable_intr(sc->vtblk_vq);
369227652Sgrehan
370227652Sgrehanfail:
371227652Sgrehan	if (error)
372227652Sgrehan		vtblk_detach(dev);
373227652Sgrehan
374227652Sgrehan	return (error);
375227652Sgrehan}
376227652Sgrehan
377227652Sgrehanstatic int
378227652Sgrehanvtblk_detach(device_t dev)
379227652Sgrehan{
380227652Sgrehan	struct vtblk_softc *sc;
381227652Sgrehan
382227652Sgrehan	sc = device_get_softc(dev);
383227652Sgrehan
384227652Sgrehan	VTBLK_LOCK(sc);
385239472Semaste	sc->vtblk_flags |= VTBLK_FLAG_DETACH;
386227652Sgrehan	if (device_is_attached(dev))
387227652Sgrehan		vtblk_stop(sc);
388227652Sgrehan	VTBLK_UNLOCK(sc);
389227652Sgrehan
390227652Sgrehan	vtblk_drain(sc);
391227652Sgrehan
392227652Sgrehan	if (sc->vtblk_disk != NULL) {
393227652Sgrehan		disk_destroy(sc->vtblk_disk);
394227652Sgrehan		sc->vtblk_disk = NULL;
395227652Sgrehan	}
396227652Sgrehan
397227652Sgrehan	if (sc->vtblk_sglist != NULL) {
398227652Sgrehan		sglist_free(sc->vtblk_sglist);
399227652Sgrehan		sc->vtblk_sglist = NULL;
400227652Sgrehan	}
401227652Sgrehan
402227652Sgrehan	VTBLK_LOCK_DESTROY(sc);
403227652Sgrehan
404227652Sgrehan	return (0);
405227652Sgrehan}
406227652Sgrehan
407227652Sgrehanstatic int
408227652Sgrehanvtblk_suspend(device_t dev)
409227652Sgrehan{
410227652Sgrehan	struct vtblk_softc *sc;
411239472Semaste	int error;
412227652Sgrehan
413227652Sgrehan	sc = device_get_softc(dev);
414227652Sgrehan
415227652Sgrehan	VTBLK_LOCK(sc);
416239472Semaste	sc->vtblk_flags |= VTBLK_FLAG_SUSPEND;
417239472Semaste	/* XXX BMV: virtio_stop(), etc needed here? */
418239472Semaste	error = vtblk_quiesce(sc);
419239472Semaste	if (error)
420239472Semaste		sc->vtblk_flags &= ~VTBLK_FLAG_SUSPEND;
421227652Sgrehan	VTBLK_UNLOCK(sc);
422227652Sgrehan
423239472Semaste	return (error);
424227652Sgrehan}
425227652Sgrehan
426227652Sgrehanstatic int
427227652Sgrehanvtblk_resume(device_t dev)
428227652Sgrehan{
429227652Sgrehan	struct vtblk_softc *sc;
430227652Sgrehan
431227652Sgrehan	sc = device_get_softc(dev);
432227652Sgrehan
433227652Sgrehan	VTBLK_LOCK(sc);
434239472Semaste	/* XXX BMV: virtio_reinit(), etc needed here? */
435239472Semaste	sc->vtblk_flags &= ~VTBLK_FLAG_SUSPEND;
436239472Semaste	vtblk_startio(sc);
437227652Sgrehan	VTBLK_UNLOCK(sc);
438227652Sgrehan
439227652Sgrehan	return (0);
440227652Sgrehan}
441227652Sgrehan
442227652Sgrehanstatic int
443227652Sgrehanvtblk_shutdown(device_t dev)
444227652Sgrehan{
445227652Sgrehan
446227652Sgrehan	return (0);
447227652Sgrehan}
448227652Sgrehan
449227652Sgrehanstatic int
450253132Sbryanvvtblk_config_change(device_t dev)
451253132Sbryanv{
452253132Sbryanv
453253132Sbryanv	return (0);
454253132Sbryanv}
455253132Sbryanv
456253132Sbryanvstatic int
457227652Sgrehanvtblk_open(struct disk *dp)
458227652Sgrehan{
459227652Sgrehan	struct vtblk_softc *sc;
460227652Sgrehan
461227652Sgrehan	if ((sc = dp->d_drv1) == NULL)
462227652Sgrehan		return (ENXIO);
463227652Sgrehan
464239472Semaste	return (sc->vtblk_flags & VTBLK_FLAG_DETACH ? ENXIO : 0);
465227652Sgrehan}
466227652Sgrehan
467227652Sgrehanstatic int
468227652Sgrehanvtblk_close(struct disk *dp)
469227652Sgrehan{
470227652Sgrehan	struct vtblk_softc *sc;
471227652Sgrehan
472227652Sgrehan	if ((sc = dp->d_drv1) == NULL)
473227652Sgrehan		return (ENXIO);
474227652Sgrehan
475227652Sgrehan	return (0);
476227652Sgrehan}
477227652Sgrehan
478227652Sgrehanstatic int
479227652Sgrehanvtblk_ioctl(struct disk *dp, u_long cmd, void *addr, int flag,
480227652Sgrehan    struct thread *td)
481227652Sgrehan{
482227652Sgrehan	struct vtblk_softc *sc;
483227652Sgrehan
484227652Sgrehan	if ((sc = dp->d_drv1) == NULL)
485227652Sgrehan		return (ENXIO);
486227652Sgrehan
487227652Sgrehan	return (ENOTTY);
488227652Sgrehan}
489227652Sgrehan
490227652Sgrehanstatic int
491227652Sgrehanvtblk_dump(void *arg, void *virtual, vm_offset_t physical, off_t offset,
492227652Sgrehan    size_t length)
493227652Sgrehan{
494227652Sgrehan	struct disk *dp;
495227652Sgrehan	struct vtblk_softc *sc;
496227652Sgrehan	int error;
497227652Sgrehan
498227652Sgrehan	dp = arg;
499227652Sgrehan
500227652Sgrehan	if ((sc = dp->d_drv1) == NULL)
501227652Sgrehan		return (ENXIO);
502227652Sgrehan
503239472Semaste	VTBLK_LOCK(sc);
504227652Sgrehan
505227652Sgrehan	if ((sc->vtblk_flags & VTBLK_FLAG_DUMPING) == 0) {
506227652Sgrehan		vtblk_prepare_dump(sc);
507227652Sgrehan		sc->vtblk_flags |= VTBLK_FLAG_DUMPING;
508227652Sgrehan	}
509227652Sgrehan
510227652Sgrehan	if (length > 0)
511227652Sgrehan		error = vtblk_write_dump(sc, virtual, offset, length);
512227652Sgrehan	else if (virtual == NULL && offset == 0)
513227652Sgrehan		error = vtblk_flush_dump(sc);
514239472Semaste	else {
515239472Semaste		error = EINVAL;
516239472Semaste		sc->vtblk_flags &= ~VTBLK_FLAG_DUMPING;
517239472Semaste	}
518227652Sgrehan
519227652Sgrehan	VTBLK_UNLOCK(sc);
520227652Sgrehan
521227652Sgrehan	return (error);
522227652Sgrehan}
523227652Sgrehan
524227652Sgrehanstatic void
525227652Sgrehanvtblk_strategy(struct bio *bp)
526227652Sgrehan{
527227652Sgrehan	struct vtblk_softc *sc;
528227652Sgrehan
529227652Sgrehan	if ((sc = bp->bio_disk->d_drv1) == NULL) {
530239472Semaste		vtblk_finish_bio(bp, EINVAL);
531227652Sgrehan		return;
532227652Sgrehan	}
533227652Sgrehan
534227652Sgrehan	/*
535227652Sgrehan	 * Fail any write if RO. Unfortunately, there does not seem to
536227652Sgrehan	 * be a better way to report our readonly'ness to GEOM above.
537227652Sgrehan	 */
538227652Sgrehan	if (sc->vtblk_flags & VTBLK_FLAG_READONLY &&
539227652Sgrehan	    (bp->bio_cmd == BIO_WRITE || bp->bio_cmd == BIO_FLUSH)) {
540239472Semaste		vtblk_finish_bio(bp, EROFS);
541227652Sgrehan		return;
542227652Sgrehan	}
543227652Sgrehan
544246582Sbryanv#ifdef INVARIANTS
545227652Sgrehan	/*
546227652Sgrehan	 * Prevent read/write buffers spanning too many segments from
547227652Sgrehan	 * getting into the queue. This should only trip if d_maxsize
548227652Sgrehan	 * was incorrectly set.
549227652Sgrehan	 */
550227652Sgrehan	if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE) {
551239472Semaste		int nsegs, max_nsegs;
552246582Sbryanv
553239472Semaste		nsegs = sglist_count(bp->bio_data, bp->bio_bcount);
554239472Semaste		max_nsegs = sc->vtblk_max_nsegs - VTBLK_MIN_SEGMENTS;
555239472Semaste
556239472Semaste		KASSERT(nsegs <= max_nsegs,
557253132Sbryanv		    ("%s: bio %p spanned too many segments: %d, max: %d",
558253132Sbryanv		    __func__, bp, nsegs, max_nsegs));
559227652Sgrehan	}
560239472Semaste#endif
561227652Sgrehan
562227652Sgrehan	VTBLK_LOCK(sc);
563239472Semaste	if (sc->vtblk_flags & VTBLK_FLAG_DETACH)
564239472Semaste		vtblk_finish_bio(bp, ENXIO);
565239472Semaste	else {
566227652Sgrehan		bioq_disksort(&sc->vtblk_bioq, bp);
567239472Semaste
568239472Semaste		if ((sc->vtblk_flags & VTBLK_FLAG_SUSPEND) == 0)
569239472Semaste			vtblk_startio(sc);
570239472Semaste	}
571227652Sgrehan	VTBLK_UNLOCK(sc);
572227652Sgrehan}
573227652Sgrehan
574227652Sgrehanstatic void
575227652Sgrehanvtblk_negotiate_features(struct vtblk_softc *sc)
576227652Sgrehan{
577227652Sgrehan	device_t dev;
578227652Sgrehan	uint64_t features;
579227652Sgrehan
580227652Sgrehan	dev = sc->vtblk_dev;
581227652Sgrehan	features = VTBLK_FEATURES;
582227652Sgrehan
583227652Sgrehan	sc->vtblk_features = virtio_negotiate_features(dev, features);
584227652Sgrehan}
585227652Sgrehan
586227652Sgrehanstatic int
587227652Sgrehanvtblk_maximum_segments(struct vtblk_softc *sc,
588227652Sgrehan    struct virtio_blk_config *blkcfg)
589227652Sgrehan{
590227652Sgrehan	device_t dev;
591227652Sgrehan	int nsegs;
592227652Sgrehan
593227652Sgrehan	dev = sc->vtblk_dev;
594227652Sgrehan	nsegs = VTBLK_MIN_SEGMENTS;
595227652Sgrehan
596227652Sgrehan	if (virtio_with_feature(dev, VIRTIO_BLK_F_SEG_MAX)) {
597227652Sgrehan		nsegs += MIN(blkcfg->seg_max, MAXPHYS / PAGE_SIZE + 1);
598227652Sgrehan		if (sc->vtblk_flags & VTBLK_FLAG_INDIRECT)
599227652Sgrehan			nsegs = MIN(nsegs, VIRTIO_MAX_INDIRECT);
600227652Sgrehan	} else
601227652Sgrehan		nsegs += 1;
602227652Sgrehan
603227652Sgrehan	return (nsegs);
604227652Sgrehan}
605227652Sgrehan
606227652Sgrehanstatic int
607227652Sgrehanvtblk_alloc_virtqueue(struct vtblk_softc *sc)
608227652Sgrehan{
609227652Sgrehan	device_t dev;
610227652Sgrehan	struct vq_alloc_info vq_info;
611227652Sgrehan
612227652Sgrehan	dev = sc->vtblk_dev;
613227652Sgrehan
614227652Sgrehan	VQ_ALLOC_INFO_INIT(&vq_info, sc->vtblk_max_nsegs,
615227652Sgrehan	    vtblk_vq_intr, sc, &sc->vtblk_vq,
616227652Sgrehan	    "%s request", device_get_nameunit(dev));
617227652Sgrehan
618227652Sgrehan	return (virtio_alloc_virtqueues(dev, 0, 1, &vq_info));
619227652Sgrehan}
620227652Sgrehan
621227652Sgrehanstatic void
622253132Sbryanvvtblk_set_write_cache(struct vtblk_softc *sc, int wc)
623253132Sbryanv{
624253132Sbryanv
625253132Sbryanv	/* Set either writeback (1) or writethrough (0) mode. */
626253132Sbryanv	virtio_write_dev_config_1(sc->vtblk_dev,
627253132Sbryanv	    offsetof(struct virtio_blk_config, writeback), wc);
628253132Sbryanv}
629253132Sbryanv
630253132Sbryanvstatic int
631253132Sbryanvvtblk_write_cache_enabled(struct vtblk_softc *sc,
632253132Sbryanv    struct virtio_blk_config *blkcfg)
633253132Sbryanv{
634253132Sbryanv	int wc;
635253132Sbryanv
636253132Sbryanv	if (sc->vtblk_flags & VTBLK_FLAG_WC_CONFIG) {
637253132Sbryanv		wc = vtblk_tunable_int(sc, "writecache_mode",
638253132Sbryanv		    vtblk_writecache_mode);
639253132Sbryanv		if (wc >= 0 && wc < VTBLK_CACHE_MAX)
640253132Sbryanv			vtblk_set_write_cache(sc, wc);
641253132Sbryanv		else
642253132Sbryanv			wc = blkcfg->writeback;
643253132Sbryanv	} else
644253132Sbryanv		wc = virtio_with_feature(sc->vtblk_dev, VIRTIO_BLK_F_WCE);
645253132Sbryanv
646253132Sbryanv	return (wc);
647253132Sbryanv}
648253132Sbryanv
649253132Sbryanvstatic int
650253132Sbryanvvtblk_write_cache_sysctl(SYSCTL_HANDLER_ARGS)
651253132Sbryanv{
652253132Sbryanv	struct vtblk_softc *sc;
653253132Sbryanv	int wc, error;
654253132Sbryanv
655253132Sbryanv	sc = oidp->oid_arg1;
656253132Sbryanv	wc = sc->vtblk_write_cache;
657253132Sbryanv
658253132Sbryanv	error = sysctl_handle_int(oidp, &wc, 0, req);
659253132Sbryanv	if (error || req->newptr == NULL)
660253132Sbryanv		return (error);
661253132Sbryanv	if ((sc->vtblk_flags & VTBLK_FLAG_WC_CONFIG) == 0)
662253132Sbryanv		return (EPERM);
663253132Sbryanv	if (wc < 0 || wc >= VTBLK_CACHE_MAX)
664253132Sbryanv		return (EINVAL);
665253132Sbryanv
666253132Sbryanv	VTBLK_LOCK(sc);
667253132Sbryanv	sc->vtblk_write_cache = wc;
668253132Sbryanv	vtblk_set_write_cache(sc, sc->vtblk_write_cache);
669253132Sbryanv	VTBLK_UNLOCK(sc);
670253132Sbryanv
671253132Sbryanv	return (0);
672253132Sbryanv}
673253132Sbryanv
674253132Sbryanvstatic void
675227652Sgrehanvtblk_alloc_disk(struct vtblk_softc *sc, struct virtio_blk_config *blkcfg)
676227652Sgrehan{
677227652Sgrehan	device_t dev;
678227652Sgrehan	struct disk *dp;
679227652Sgrehan
680227652Sgrehan	dev = sc->vtblk_dev;
681227652Sgrehan
682227652Sgrehan	sc->vtblk_disk = dp = disk_alloc();
683227652Sgrehan	dp->d_open = vtblk_open;
684227652Sgrehan	dp->d_close = vtblk_close;
685227652Sgrehan	dp->d_ioctl = vtblk_ioctl;
686227652Sgrehan	dp->d_strategy = vtblk_strategy;
687227652Sgrehan	dp->d_name = VTBLK_DISK_NAME;
688239472Semaste	dp->d_unit = device_get_unit(dev);
689227652Sgrehan	dp->d_drv1 = sc;
690265301Sbryanv	dp->d_flags = DISKFLAG_CANFLUSHCACHE | DISKFLAG_UNMAPPED_BIO;
691253132Sbryanv	dp->d_hba_vendor = virtio_get_vendor(dev);
692253132Sbryanv	dp->d_hba_device = virtio_get_device(dev);
693253132Sbryanv	dp->d_hba_subvendor = virtio_get_subvendor(dev);
694253132Sbryanv	dp->d_hba_subdevice = virtio_get_subdevice(dev);
695227652Sgrehan
696227652Sgrehan	if ((sc->vtblk_flags & VTBLK_FLAG_READONLY) == 0)
697227652Sgrehan		dp->d_dump = vtblk_dump;
698227652Sgrehan
699227652Sgrehan	/* Capacity is always in 512-byte units. */
700227652Sgrehan	dp->d_mediasize = blkcfg->capacity * 512;
701227652Sgrehan
702227652Sgrehan	if (virtio_with_feature(dev, VIRTIO_BLK_F_BLK_SIZE))
703239472Semaste		dp->d_sectorsize = blkcfg->blk_size;
704227652Sgrehan	else
705239472Semaste		dp->d_sectorsize = 512;
706227652Sgrehan
707227652Sgrehan	/*
708227652Sgrehan	 * The VirtIO maximum I/O size is given in terms of segments.
709227652Sgrehan	 * However, FreeBSD limits I/O size by logical buffer size, not
710227652Sgrehan	 * by physically contiguous pages. Therefore, we have to assume
711227652Sgrehan	 * no pages are contiguous. This may impose an artificially low
712227652Sgrehan	 * maximum I/O size. But in practice, since QEMU advertises 128
713227652Sgrehan	 * segments, this gives us a maximum IO size of 125 * PAGE_SIZE,
714227652Sgrehan	 * which is typically greater than MAXPHYS. Eventually we should
715227652Sgrehan	 * just advertise MAXPHYS and split buffers that are too big.
716227652Sgrehan	 *
717227652Sgrehan	 * Note we must subtract one additional segment in case of non
718227652Sgrehan	 * page aligned buffers.
719227652Sgrehan	 */
720227652Sgrehan	dp->d_maxsize = (sc->vtblk_max_nsegs - VTBLK_MIN_SEGMENTS - 1) *
721227652Sgrehan	    PAGE_SIZE;
722227652Sgrehan	if (dp->d_maxsize < PAGE_SIZE)
723227652Sgrehan		dp->d_maxsize = PAGE_SIZE; /* XXX */
724227652Sgrehan
725227652Sgrehan	if (virtio_with_feature(dev, VIRTIO_BLK_F_GEOMETRY)) {
726227652Sgrehan		dp->d_fwsectors = blkcfg->geometry.sectors;
727227652Sgrehan		dp->d_fwheads = blkcfg->geometry.heads;
728227652Sgrehan	}
729227652Sgrehan
730253132Sbryanv	if (virtio_with_feature(dev, VIRTIO_BLK_F_TOPOLOGY)) {
731253132Sbryanv		dp->d_stripesize = dp->d_sectorsize *
732253132Sbryanv		    (1 << blkcfg->topology.physical_block_exp);
733253132Sbryanv		dp->d_stripeoffset = (dp->d_stripesize -
734253132Sbryanv		    blkcfg->topology.alignment_offset * dp->d_sectorsize) %
735253132Sbryanv		    dp->d_stripesize;
736253132Sbryanv	}
737253132Sbryanv
738253132Sbryanv	if (vtblk_write_cache_enabled(sc, blkcfg) != 0)
739253132Sbryanv		sc->vtblk_write_cache = VTBLK_CACHE_WRITEBACK;
740253132Sbryanv	else
741253132Sbryanv		sc->vtblk_write_cache = VTBLK_CACHE_WRITETHROUGH;
742227652Sgrehan}
743227652Sgrehan
744227652Sgrehanstatic void
745227652Sgrehanvtblk_create_disk(struct vtblk_softc *sc)
746227652Sgrehan{
747227652Sgrehan	struct disk *dp;
748227652Sgrehan
749227652Sgrehan	dp = sc->vtblk_disk;
750227652Sgrehan
751227652Sgrehan	/*
752227652Sgrehan	 * Retrieving the identification string must be done after
753227652Sgrehan	 * the virtqueue interrupt is setup otherwise it will hang.
754227652Sgrehan	 */
755227652Sgrehan	vtblk_get_ident(sc);
756227652Sgrehan
757227652Sgrehan	device_printf(sc->vtblk_dev, "%juMB (%ju %u byte sectors)\n",
758227652Sgrehan	    (uintmax_t) dp->d_mediasize >> 20,
759227652Sgrehan	    (uintmax_t) dp->d_mediasize / dp->d_sectorsize,
760227652Sgrehan	    dp->d_sectorsize);
761227652Sgrehan
762227652Sgrehan	disk_create(dp, DISK_VERSION);
763227652Sgrehan}
764227652Sgrehan
765239472Semastestatic int
766239472Semastevtblk_quiesce(struct vtblk_softc *sc)
767239472Semaste{
768239472Semaste	int error;
769239472Semaste
770239472Semaste	error = 0;
771239472Semaste
772239472Semaste	VTBLK_LOCK_ASSERT(sc);
773239472Semaste
774239472Semaste	while (!virtqueue_empty(sc->vtblk_vq)) {
775239472Semaste		if (mtx_sleep(&sc->vtblk_vq, VTBLK_MTX(sc), PRIBIO, "vtblkq",
776239472Semaste		    VTBLK_QUIESCE_TIMEOUT) == EWOULDBLOCK) {
777239472Semaste			error = EBUSY;
778239472Semaste			break;
779239472Semaste		}
780239472Semaste	}
781239472Semaste
782239472Semaste	return (error);
783239472Semaste}
784239472Semaste
785227652Sgrehanstatic void
786227652Sgrehanvtblk_startio(struct vtblk_softc *sc)
787227652Sgrehan{
788227652Sgrehan	struct virtqueue *vq;
789227652Sgrehan	struct vtblk_request *req;
790227652Sgrehan	int enq;
791227652Sgrehan
792227652Sgrehan	vq = sc->vtblk_vq;
793227652Sgrehan	enq = 0;
794227652Sgrehan
795227652Sgrehan	VTBLK_LOCK_ASSERT(sc);
796227652Sgrehan
797227652Sgrehan	while (!virtqueue_full(vq)) {
798227652Sgrehan		if ((req = vtblk_dequeue_ready(sc)) == NULL)
799227652Sgrehan			req = vtblk_bio_request(sc);
800227652Sgrehan		if (req == NULL)
801227652Sgrehan			break;
802227652Sgrehan
803227652Sgrehan		if (vtblk_execute_request(sc, req) != 0) {
804227652Sgrehan			vtblk_enqueue_ready(sc, req);
805227652Sgrehan			break;
806227652Sgrehan		}
807227652Sgrehan
808227652Sgrehan		enq++;
809227652Sgrehan	}
810227652Sgrehan
811227652Sgrehan	if (enq > 0)
812227652Sgrehan		virtqueue_notify(vq);
813227652Sgrehan}
814227652Sgrehan
815227652Sgrehanstatic struct vtblk_request *
816227652Sgrehanvtblk_bio_request(struct vtblk_softc *sc)
817227652Sgrehan{
818227652Sgrehan	struct bio_queue_head *bioq;
819227652Sgrehan	struct vtblk_request *req;
820227652Sgrehan	struct bio *bp;
821227652Sgrehan
822227652Sgrehan	bioq = &sc->vtblk_bioq;
823227652Sgrehan
824227652Sgrehan	if (bioq_first(bioq) == NULL)
825227652Sgrehan		return (NULL);
826227652Sgrehan
827227652Sgrehan	req = vtblk_dequeue_request(sc);
828227652Sgrehan	if (req == NULL)
829227652Sgrehan		return (NULL);
830227652Sgrehan
831227652Sgrehan	bp = bioq_takefirst(bioq);
832227652Sgrehan	req->vbr_bp = bp;
833227652Sgrehan	req->vbr_ack = -1;
834227652Sgrehan	req->vbr_hdr.ioprio = 1;
835227652Sgrehan
836227652Sgrehan	switch (bp->bio_cmd) {
837227652Sgrehan	case BIO_FLUSH:
838227652Sgrehan		req->vbr_hdr.type = VIRTIO_BLK_T_FLUSH;
839227652Sgrehan		break;
840227652Sgrehan	case BIO_READ:
841227652Sgrehan		req->vbr_hdr.type = VIRTIO_BLK_T_IN;
842227652Sgrehan		req->vbr_hdr.sector = bp->bio_offset / 512;
843227652Sgrehan		break;
844227652Sgrehan	case BIO_WRITE:
845227652Sgrehan		req->vbr_hdr.type = VIRTIO_BLK_T_OUT;
846227652Sgrehan		req->vbr_hdr.sector = bp->bio_offset / 512;
847227652Sgrehan		break;
848227652Sgrehan	default:
849253132Sbryanv		panic("%s: bio with unhandled cmd: %d", __func__, bp->bio_cmd);
850227652Sgrehan	}
851227652Sgrehan
852227652Sgrehan	return (req);
853227652Sgrehan}
854227652Sgrehan
855227652Sgrehanstatic int
856227652Sgrehanvtblk_execute_request(struct vtblk_softc *sc, struct vtblk_request *req)
857227652Sgrehan{
858248026Sbryanv	struct virtqueue *vq;
859227652Sgrehan	struct sglist *sg;
860227652Sgrehan	struct bio *bp;
861248026Sbryanv	int ordered, readable, writable, error;
862227652Sgrehan
863248026Sbryanv	vq = sc->vtblk_vq;
864227652Sgrehan	sg = sc->vtblk_sglist;
865227652Sgrehan	bp = req->vbr_bp;
866248026Sbryanv	ordered = 0;
867227652Sgrehan	writable = 0;
868227652Sgrehan
869227652Sgrehan	VTBLK_LOCK_ASSERT(sc);
870227652Sgrehan
871248026Sbryanv	/*
872248026Sbryanv	 * Wait until the ordered request completes before
873248026Sbryanv	 * executing subsequent requests.
874248026Sbryanv	 */
875248026Sbryanv	if (sc->vtblk_req_ordered != NULL)
876248026Sbryanv		return (EBUSY);
877248026Sbryanv
878248026Sbryanv	if (bp->bio_flags & BIO_ORDERED) {
879248026Sbryanv		if ((sc->vtblk_flags & VTBLK_FLAG_BARRIER) == 0) {
880248026Sbryanv			/*
881248026Sbryanv			 * This request will be executed once all
882248026Sbryanv			 * the in-flight requests are completed.
883248026Sbryanv			 */
884248026Sbryanv			if (!virtqueue_empty(vq))
885248026Sbryanv				return (EBUSY);
886248026Sbryanv			ordered = 1;
887248026Sbryanv		} else
888248026Sbryanv			req->vbr_hdr.type |= VIRTIO_BLK_T_BARRIER;
889248026Sbryanv	}
890248026Sbryanv
891227652Sgrehan	sglist_reset(sg);
892246582Sbryanv	sglist_append(sg, &req->vbr_hdr, sizeof(struct virtio_blk_outhdr));
893246582Sbryanv
894227652Sgrehan	if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE) {
895265301Sbryanv		error = sglist_append_bio(sg, bp);
896265301Sbryanv		if (error || sg->sg_nseg == sg->sg_maxseg) {
897246582Sbryanv			panic("%s: data buffer too big bio:%p error:%d",
898253132Sbryanv			    __func__, bp, error);
899265301Sbryanv		}
900227652Sgrehan
901227652Sgrehan		/* BIO_READ means the host writes into our buffer. */
902227652Sgrehan		if (bp->bio_cmd == BIO_READ)
903246582Sbryanv			writable = sg->sg_nseg - 1;
904227652Sgrehan	}
905227652Sgrehan
906227652Sgrehan	writable++;
907246582Sbryanv	sglist_append(sg, &req->vbr_ack, sizeof(uint8_t));
908239472Semaste	readable = sg->sg_nseg - writable;
909227652Sgrehan
910248026Sbryanv	error = virtqueue_enqueue(vq, req, sg, readable, writable);
911248026Sbryanv	if (error == 0 && ordered)
912248026Sbryanv		sc->vtblk_req_ordered = req;
913248026Sbryanv
914248026Sbryanv	return (error);
915227652Sgrehan}
916227652Sgrehan
917253132Sbryanvstatic void
918227652Sgrehanvtblk_vq_intr(void *xsc)
919227652Sgrehan{
920227652Sgrehan	struct vtblk_softc *sc;
921253132Sbryanv	struct virtqueue *vq;
922227652Sgrehan
923227652Sgrehan	sc = xsc;
924227652Sgrehan	vq = sc->vtblk_vq;
925227652Sgrehan
926253132Sbryanvagain:
927227652Sgrehan	VTBLK_LOCK(sc);
928239472Semaste	if (sc->vtblk_flags & VTBLK_FLAG_DETACH) {
929227652Sgrehan		VTBLK_UNLOCK(sc);
930227652Sgrehan		return;
931227652Sgrehan	}
932227652Sgrehan
933239472Semaste	vtblk_finish_completed(sc);
934227652Sgrehan
935239472Semaste	if ((sc->vtblk_flags & VTBLK_FLAG_SUSPEND) == 0)
936239472Semaste		vtblk_startio(sc);
937239472Semaste	else
938239472Semaste		wakeup(&sc->vtblk_vq);
939227652Sgrehan
940227652Sgrehan	if (virtqueue_enable_intr(vq) != 0) {
941227652Sgrehan		virtqueue_disable_intr(vq);
942227652Sgrehan		VTBLK_UNLOCK(sc);
943253132Sbryanv		goto again;
944227652Sgrehan	}
945227652Sgrehan
946227652Sgrehan	VTBLK_UNLOCK(sc);
947227652Sgrehan}
948227652Sgrehan
949227652Sgrehanstatic void
950227652Sgrehanvtblk_stop(struct vtblk_softc *sc)
951227652Sgrehan{
952227652Sgrehan
953227652Sgrehan	virtqueue_disable_intr(sc->vtblk_vq);
954227652Sgrehan	virtio_stop(sc->vtblk_dev);
955227652Sgrehan}
956227652Sgrehan
957253132Sbryanv#define VTBLK_GET_CONFIG(_dev, _feature, _field, _cfg)			\
958253132Sbryanv	if (virtio_with_feature(_dev, _feature)) {			\
959253132Sbryanv		virtio_read_device_config(_dev,				\
960253132Sbryanv		    offsetof(struct virtio_blk_config, _field),		\
961253132Sbryanv		    &(_cfg)->_field, sizeof((_cfg)->_field));		\
962253132Sbryanv	}
963253132Sbryanv
964227652Sgrehanstatic void
965253132Sbryanvvtblk_read_config(struct vtblk_softc *sc, struct virtio_blk_config *blkcfg)
966253132Sbryanv{
967253132Sbryanv	device_t dev;
968253132Sbryanv
969253132Sbryanv	dev = sc->vtblk_dev;
970253132Sbryanv
971253132Sbryanv	bzero(blkcfg, sizeof(struct virtio_blk_config));
972253132Sbryanv
973253132Sbryanv	/* The capacity is always available. */
974253132Sbryanv	virtio_read_device_config(dev, offsetof(struct virtio_blk_config,
975253132Sbryanv	    capacity), &blkcfg->capacity, sizeof(blkcfg->capacity));
976253132Sbryanv
977253132Sbryanv	/* Read the configuration if the feature was negotiated. */
978253132Sbryanv	VTBLK_GET_CONFIG(dev, VIRTIO_BLK_F_SIZE_MAX, size_max, blkcfg);
979253132Sbryanv	VTBLK_GET_CONFIG(dev, VIRTIO_BLK_F_SEG_MAX, seg_max, blkcfg);
980253132Sbryanv	VTBLK_GET_CONFIG(dev, VIRTIO_BLK_F_GEOMETRY, geometry, blkcfg);
981253132Sbryanv	VTBLK_GET_CONFIG(dev, VIRTIO_BLK_F_BLK_SIZE, blk_size, blkcfg);
982253132Sbryanv	VTBLK_GET_CONFIG(dev, VIRTIO_BLK_F_TOPOLOGY, topology, blkcfg);
983253132Sbryanv	VTBLK_GET_CONFIG(dev, VIRTIO_BLK_F_CONFIG_WCE, writeback, blkcfg);
984253132Sbryanv}
985253132Sbryanv
986253132Sbryanv#undef VTBLK_GET_CONFIG
987253132Sbryanv
988253132Sbryanvstatic void
989227652Sgrehanvtblk_get_ident(struct vtblk_softc *sc)
990227652Sgrehan{
991227652Sgrehan	struct bio buf;
992227652Sgrehan	struct disk *dp;
993227652Sgrehan	struct vtblk_request *req;
994227652Sgrehan	int len, error;
995227652Sgrehan
996227652Sgrehan	dp = sc->vtblk_disk;
997227652Sgrehan	len = MIN(VIRTIO_BLK_ID_BYTES, DISK_IDENT_SIZE);
998227652Sgrehan
999253132Sbryanv	if (vtblk_tunable_int(sc, "no_ident", vtblk_no_ident) != 0)
1000227652Sgrehan		return;
1001227652Sgrehan
1002227652Sgrehan	req = vtblk_dequeue_request(sc);
1003227652Sgrehan	if (req == NULL)
1004227652Sgrehan		return;
1005227652Sgrehan
1006227652Sgrehan	req->vbr_ack = -1;
1007227652Sgrehan	req->vbr_hdr.type = VIRTIO_BLK_T_GET_ID;
1008227652Sgrehan	req->vbr_hdr.ioprio = 1;
1009227652Sgrehan	req->vbr_hdr.sector = 0;
1010227652Sgrehan
1011227652Sgrehan	req->vbr_bp = &buf;
1012227652Sgrehan	bzero(&buf, sizeof(struct bio));
1013227652Sgrehan
1014227652Sgrehan	buf.bio_cmd = BIO_READ;
1015227652Sgrehan	buf.bio_data = dp->d_ident;
1016227652Sgrehan	buf.bio_bcount = len;
1017227652Sgrehan
1018227652Sgrehan	VTBLK_LOCK(sc);
1019227652Sgrehan	error = vtblk_poll_request(sc, req);
1020227652Sgrehan	VTBLK_UNLOCK(sc);
1021227652Sgrehan
1022239472Semaste	vtblk_enqueue_request(sc, req);
1023239472Semaste
1024227652Sgrehan	if (error) {
1025227652Sgrehan		device_printf(sc->vtblk_dev,
1026227652Sgrehan		    "error getting device identifier: %d\n", error);
1027227652Sgrehan	}
1028227652Sgrehan}
1029227652Sgrehan
1030227652Sgrehanstatic void
1031227652Sgrehanvtblk_prepare_dump(struct vtblk_softc *sc)
1032227652Sgrehan{
1033227652Sgrehan	device_t dev;
1034227652Sgrehan	struct virtqueue *vq;
1035227652Sgrehan
1036227652Sgrehan	dev = sc->vtblk_dev;
1037227652Sgrehan	vq = sc->vtblk_vq;
1038227652Sgrehan
1039227652Sgrehan	vtblk_stop(sc);
1040227652Sgrehan
1041227652Sgrehan	/*
1042227652Sgrehan	 * Drain all requests caught in-flight in the virtqueue,
1043227652Sgrehan	 * skipping biodone(). When dumping, only one request is
1044227652Sgrehan	 * outstanding at a time, and we just poll the virtqueue
1045227652Sgrehan	 * for the response.
1046227652Sgrehan	 */
1047227652Sgrehan	vtblk_drain_vq(sc, 1);
1048227652Sgrehan
1049253132Sbryanv	if (virtio_reinit(dev, sc->vtblk_features) != 0) {
1050253132Sbryanv		panic("%s: cannot reinit VirtIO block device during dump",
1051253132Sbryanv		    device_get_nameunit(dev));
1052253132Sbryanv	}
1053227652Sgrehan
1054227652Sgrehan	virtqueue_disable_intr(vq);
1055227652Sgrehan	virtio_reinit_complete(dev);
1056227652Sgrehan}
1057227652Sgrehan
1058227652Sgrehanstatic int
1059227652Sgrehanvtblk_write_dump(struct vtblk_softc *sc, void *virtual, off_t offset,
1060227652Sgrehan    size_t length)
1061227652Sgrehan{
1062227652Sgrehan	struct bio buf;
1063227652Sgrehan	struct vtblk_request *req;
1064227652Sgrehan
1065227652Sgrehan	req = &sc->vtblk_dump_request;
1066227652Sgrehan	req->vbr_ack = -1;
1067227652Sgrehan	req->vbr_hdr.type = VIRTIO_BLK_T_OUT;
1068227652Sgrehan	req->vbr_hdr.ioprio = 1;
1069227652Sgrehan	req->vbr_hdr.sector = offset / 512;
1070227652Sgrehan
1071227652Sgrehan	req->vbr_bp = &buf;
1072227652Sgrehan	bzero(&buf, sizeof(struct bio));
1073227652Sgrehan
1074227652Sgrehan	buf.bio_cmd = BIO_WRITE;
1075227652Sgrehan	buf.bio_data = virtual;
1076227652Sgrehan	buf.bio_bcount = length;
1077227652Sgrehan
1078227652Sgrehan	return (vtblk_poll_request(sc, req));
1079227652Sgrehan}
1080227652Sgrehan
1081227652Sgrehanstatic int
1082227652Sgrehanvtblk_flush_dump(struct vtblk_softc *sc)
1083227652Sgrehan{
1084227652Sgrehan	struct bio buf;
1085227652Sgrehan	struct vtblk_request *req;
1086227652Sgrehan
1087227652Sgrehan	req = &sc->vtblk_dump_request;
1088227652Sgrehan	req->vbr_ack = -1;
1089227652Sgrehan	req->vbr_hdr.type = VIRTIO_BLK_T_FLUSH;
1090227652Sgrehan	req->vbr_hdr.ioprio = 1;
1091227652Sgrehan	req->vbr_hdr.sector = 0;
1092227652Sgrehan
1093227652Sgrehan	req->vbr_bp = &buf;
1094227652Sgrehan	bzero(&buf, sizeof(struct bio));
1095227652Sgrehan
1096227652Sgrehan	buf.bio_cmd = BIO_FLUSH;
1097227652Sgrehan
1098227652Sgrehan	return (vtblk_poll_request(sc, req));
1099227652Sgrehan}
1100227652Sgrehan
1101227652Sgrehanstatic int
1102227652Sgrehanvtblk_poll_request(struct vtblk_softc *sc, struct vtblk_request *req)
1103227652Sgrehan{
1104227652Sgrehan	struct virtqueue *vq;
1105227652Sgrehan	int error;
1106227652Sgrehan
1107227652Sgrehan	vq = sc->vtblk_vq;
1108227652Sgrehan
1109227652Sgrehan	if (!virtqueue_empty(vq))
1110227652Sgrehan		return (EBUSY);
1111227652Sgrehan
1112227652Sgrehan	error = vtblk_execute_request(sc, req);
1113227652Sgrehan	if (error)
1114227652Sgrehan		return (error);
1115227652Sgrehan
1116227652Sgrehan	virtqueue_notify(vq);
1117253132Sbryanv	virtqueue_poll(vq, NULL);
1118227652Sgrehan
1119239472Semaste	error = vtblk_request_error(req);
1120239472Semaste	if (error && bootverbose) {
1121246582Sbryanv		device_printf(sc->vtblk_dev,
1122253132Sbryanv		    "%s: IO error: %d\n", __func__, error);
1123227652Sgrehan	}
1124227652Sgrehan
1125227652Sgrehan	return (error);
1126227652Sgrehan}
1127227652Sgrehan
1128227652Sgrehanstatic void
1129239472Semastevtblk_finish_completed(struct vtblk_softc *sc)
1130239472Semaste{
1131239472Semaste	struct vtblk_request *req;
1132239472Semaste	struct bio *bp;
1133239472Semaste	int error;
1134239472Semaste
1135239472Semaste	while ((req = virtqueue_dequeue(sc->vtblk_vq, NULL)) != NULL) {
1136239472Semaste		bp = req->vbr_bp;
1137239472Semaste
1138248026Sbryanv		if (sc->vtblk_req_ordered != NULL) {
1139248026Sbryanv			/* This should be the only outstanding request. */
1140248026Sbryanv			MPASS(sc->vtblk_req_ordered == req);
1141248026Sbryanv			sc->vtblk_req_ordered = NULL;
1142248026Sbryanv		}
1143248026Sbryanv
1144239472Semaste		error = vtblk_request_error(req);
1145239472Semaste		if (error)
1146239472Semaste			disk_err(bp, "hard error", -1, 1);
1147239472Semaste
1148239472Semaste		vtblk_finish_bio(bp, error);
1149239472Semaste		vtblk_enqueue_request(sc, req);
1150239472Semaste	}
1151239472Semaste}
1152239472Semaste
1153239472Semastestatic void
1154227652Sgrehanvtblk_drain_vq(struct vtblk_softc *sc, int skip_done)
1155227652Sgrehan{
1156227652Sgrehan	struct virtqueue *vq;
1157227652Sgrehan	struct vtblk_request *req;
1158227652Sgrehan	int last;
1159227652Sgrehan
1160227652Sgrehan	vq = sc->vtblk_vq;
1161227652Sgrehan	last = 0;
1162227652Sgrehan
1163227652Sgrehan	while ((req = virtqueue_drain(vq, &last)) != NULL) {
1164227652Sgrehan		if (!skip_done)
1165239472Semaste			vtblk_finish_bio(req->vbr_bp, ENXIO);
1166227652Sgrehan
1167227652Sgrehan		vtblk_enqueue_request(sc, req);
1168227652Sgrehan	}
1169227652Sgrehan
1170248026Sbryanv	sc->vtblk_req_ordered = NULL;
1171227652Sgrehan	KASSERT(virtqueue_empty(vq), ("virtqueue not empty"));
1172227652Sgrehan}
1173227652Sgrehan
1174227652Sgrehanstatic void
1175227652Sgrehanvtblk_drain(struct vtblk_softc *sc)
1176227652Sgrehan{
1177227652Sgrehan	struct bio_queue_head *bioq;
1178227652Sgrehan	struct vtblk_request *req;
1179227652Sgrehan	struct bio *bp;
1180227652Sgrehan
1181227652Sgrehan	bioq = &sc->vtblk_bioq;
1182227652Sgrehan
1183239472Semaste	if (sc->vtblk_vq != NULL) {
1184239472Semaste		vtblk_finish_completed(sc);
1185227652Sgrehan		vtblk_drain_vq(sc, 0);
1186239472Semaste	}
1187227652Sgrehan
1188227652Sgrehan	while ((req = vtblk_dequeue_ready(sc)) != NULL) {
1189239472Semaste		vtblk_finish_bio(req->vbr_bp, ENXIO);
1190227652Sgrehan		vtblk_enqueue_request(sc, req);
1191227652Sgrehan	}
1192227652Sgrehan
1193227652Sgrehan	while (bioq_first(bioq) != NULL) {
1194227652Sgrehan		bp = bioq_takefirst(bioq);
1195239472Semaste		vtblk_finish_bio(bp, ENXIO);
1196227652Sgrehan	}
1197227652Sgrehan
1198227652Sgrehan	vtblk_free_requests(sc);
1199227652Sgrehan}
1200227652Sgrehan
1201246582Sbryanv#ifdef INVARIANTS
1202246582Sbryanvstatic void
1203246582Sbryanvvtblk_request_invariants(struct vtblk_request *req)
1204246582Sbryanv{
1205246582Sbryanv	int hdr_nsegs, ack_nsegs;
1206246582Sbryanv
1207246582Sbryanv	hdr_nsegs = sglist_count(&req->vbr_hdr, sizeof(req->vbr_hdr));
1208246582Sbryanv	ack_nsegs = sglist_count(&req->vbr_ack, sizeof(req->vbr_ack));
1209246582Sbryanv
1210246582Sbryanv	KASSERT(hdr_nsegs == 1, ("request header crossed page boundary"));
1211246582Sbryanv	KASSERT(ack_nsegs == 1, ("request ack crossed page boundary"));
1212246582Sbryanv}
1213246582Sbryanv#endif
1214246582Sbryanv
1215227652Sgrehanstatic int
1216227652Sgrehanvtblk_alloc_requests(struct vtblk_softc *sc)
1217227652Sgrehan{
1218227652Sgrehan	struct vtblk_request *req;
1219239472Semaste	int i, nreqs;
1220227652Sgrehan
1221239472Semaste	nreqs = virtqueue_size(sc->vtblk_vq);
1222227652Sgrehan
1223227652Sgrehan	/*
1224227652Sgrehan	 * Preallocate sufficient requests to keep the virtqueue full. Each
1225227652Sgrehan	 * request consumes VTBLK_MIN_SEGMENTS or more descriptors so reduce
1226227652Sgrehan	 * the number allocated when indirect descriptors are not available.
1227227652Sgrehan	 */
1228227652Sgrehan	if ((sc->vtblk_flags & VTBLK_FLAG_INDIRECT) == 0)
1229239472Semaste		nreqs /= VTBLK_MIN_SEGMENTS;
1230227652Sgrehan
1231239472Semaste	for (i = 0; i < nreqs; i++) {
1232246582Sbryanv		req = malloc(sizeof(struct vtblk_request), M_DEVBUF, M_NOWAIT);
1233227652Sgrehan		if (req == NULL)
1234227652Sgrehan			return (ENOMEM);
1235227652Sgrehan
1236246582Sbryanv#ifdef INVARIANTS
1237246582Sbryanv		vtblk_request_invariants(req);
1238246582Sbryanv#endif
1239246582Sbryanv
1240227652Sgrehan		sc->vtblk_request_count++;
1241227652Sgrehan		vtblk_enqueue_request(sc, req);
1242227652Sgrehan	}
1243227652Sgrehan
1244227652Sgrehan	return (0);
1245227652Sgrehan}
1246227652Sgrehan
1247227652Sgrehanstatic void
1248227652Sgrehanvtblk_free_requests(struct vtblk_softc *sc)
1249227652Sgrehan{
1250227652Sgrehan	struct vtblk_request *req;
1251227652Sgrehan
1252239472Semaste	KASSERT(TAILQ_EMPTY(&sc->vtblk_req_ready),
1253253132Sbryanv	    ("%s: ready requests left on queue", __func__));
1254239472Semaste
1255227652Sgrehan	while ((req = vtblk_dequeue_request(sc)) != NULL) {
1256227652Sgrehan		sc->vtblk_request_count--;
1257246582Sbryanv		free(req, M_DEVBUF);
1258227652Sgrehan	}
1259227652Sgrehan
1260246582Sbryanv	KASSERT(sc->vtblk_request_count == 0,
1261253132Sbryanv	    ("%s: leaked %d requests", __func__, sc->vtblk_request_count));
1262227652Sgrehan}
1263227652Sgrehan
1264227652Sgrehanstatic struct vtblk_request *
1265227652Sgrehanvtblk_dequeue_request(struct vtblk_softc *sc)
1266227652Sgrehan{
1267227652Sgrehan	struct vtblk_request *req;
1268227652Sgrehan
1269227652Sgrehan	req = TAILQ_FIRST(&sc->vtblk_req_free);
1270227652Sgrehan	if (req != NULL)
1271227652Sgrehan		TAILQ_REMOVE(&sc->vtblk_req_free, req, vbr_link);
1272227652Sgrehan
1273227652Sgrehan	return (req);
1274227652Sgrehan}
1275227652Sgrehan
1276227652Sgrehanstatic void
1277227652Sgrehanvtblk_enqueue_request(struct vtblk_softc *sc, struct vtblk_request *req)
1278227652Sgrehan{
1279227652Sgrehan
1280227652Sgrehan	bzero(req, sizeof(struct vtblk_request));
1281227652Sgrehan	TAILQ_INSERT_HEAD(&sc->vtblk_req_free, req, vbr_link);
1282227652Sgrehan}
1283227652Sgrehan
1284227652Sgrehanstatic struct vtblk_request *
1285227652Sgrehanvtblk_dequeue_ready(struct vtblk_softc *sc)
1286227652Sgrehan{
1287227652Sgrehan	struct vtblk_request *req;
1288227652Sgrehan
1289227652Sgrehan	req = TAILQ_FIRST(&sc->vtblk_req_ready);
1290227652Sgrehan	if (req != NULL)
1291227652Sgrehan		TAILQ_REMOVE(&sc->vtblk_req_ready, req, vbr_link);
1292227652Sgrehan
1293227652Sgrehan	return (req);
1294227652Sgrehan}
1295227652Sgrehan
1296227652Sgrehanstatic void
1297227652Sgrehanvtblk_enqueue_ready(struct vtblk_softc *sc, struct vtblk_request *req)
1298227652Sgrehan{
1299227652Sgrehan
1300227652Sgrehan	TAILQ_INSERT_HEAD(&sc->vtblk_req_ready, req, vbr_link);
1301227652Sgrehan}
1302227652Sgrehan
1303239472Semastestatic int
1304239472Semastevtblk_request_error(struct vtblk_request *req)
1305239472Semaste{
1306239472Semaste	int error;
1307239472Semaste
1308239472Semaste	switch (req->vbr_ack) {
1309239472Semaste	case VIRTIO_BLK_S_OK:
1310239472Semaste		error = 0;
1311239472Semaste		break;
1312239472Semaste	case VIRTIO_BLK_S_UNSUPP:
1313239472Semaste		error = ENOTSUP;
1314239472Semaste		break;
1315239472Semaste	default:
1316239472Semaste		error = EIO;
1317239472Semaste		break;
1318239472Semaste	}
1319239472Semaste
1320239472Semaste	return (error);
1321239472Semaste}
1322239472Semaste
1323227652Sgrehanstatic void
1324239472Semastevtblk_finish_bio(struct bio *bp, int error)
1325227652Sgrehan{
1326227652Sgrehan
1327239472Semaste	if (error) {
1328239472Semaste		bp->bio_resid = bp->bio_bcount;
1329239472Semaste		bp->bio_error = error;
1330239472Semaste		bp->bio_flags |= BIO_ERROR;
1331239472Semaste	}
1332239472Semaste
1333239472Semaste	biodone(bp);
1334227652Sgrehan}
1335253132Sbryanv
1336253132Sbryanvstatic void
1337253132Sbryanvvtblk_setup_sysctl(struct vtblk_softc *sc)
1338253132Sbryanv{
1339253132Sbryanv	device_t dev;
1340253132Sbryanv	struct sysctl_ctx_list *ctx;
1341253132Sbryanv	struct sysctl_oid *tree;
1342253132Sbryanv	struct sysctl_oid_list *child;
1343253132Sbryanv
1344253132Sbryanv	dev = sc->vtblk_dev;
1345253132Sbryanv	ctx = device_get_sysctl_ctx(dev);
1346253132Sbryanv	tree = device_get_sysctl_tree(dev);
1347253132Sbryanv	child = SYSCTL_CHILDREN(tree);
1348253132Sbryanv
1349253132Sbryanv	SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "writecache_mode",
1350253132Sbryanv	    CTLTYPE_INT | CTLFLAG_RW, sc, 0, vtblk_write_cache_sysctl,
1351253132Sbryanv	    "I", "Write cache mode (writethrough (0) or writeback (1))");
1352253132Sbryanv}
1353253132Sbryanv
1354253132Sbryanvstatic int
1355253132Sbryanvvtblk_tunable_int(struct vtblk_softc *sc, const char *knob, int def)
1356253132Sbryanv{
1357253132Sbryanv	char path[64];
1358253132Sbryanv
1359253132Sbryanv	snprintf(path, sizeof(path),
1360253132Sbryanv	    "hw.vtblk.%d.%s", device_get_unit(sc->vtblk_dev), knob);
1361253132Sbryanv	TUNABLE_INT_FETCH(path, &def);
1362253132Sbryanv
1363253132Sbryanv	return (def);
1364253132Sbryanv}
1365