summaryrefslogtreecommitdiff
path: root/drivers/virtio/virtio_blk.c
blob: e793e34e836640c283ecb524d4dfe1e71b0fa50a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
 * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
 */

#include <common.h>
#include <blk.h>
#include <dm.h>
#include <virtio_types.h>
#include <virtio.h>
#include <virtio_ring.h>
#include "virtio_blk.h"

struct virtio_blk_priv {
	struct virtqueue *vq;
};

static ulong virtio_blk_do_req(struct udevice *dev, u64 sector,
			       lbaint_t blkcnt, void *buffer, u32 type)
{
	struct virtio_blk_priv *priv = dev_get_priv(dev);
	unsigned int num_out = 0, num_in = 0;
	struct virtio_sg *sgs[3];
	u8 status;
	int ret;

	struct virtio_blk_outhdr out_hdr = {
		.type = cpu_to_virtio32(dev, type),
		.sector = cpu_to_virtio64(dev, sector),
	};
	struct virtio_sg hdr_sg = { &out_hdr, sizeof(out_hdr) };
	struct virtio_sg data_sg = { buffer, blkcnt * 512 };
	struct virtio_sg status_sg = { &status, sizeof(status) };

	sgs[num_out++] = &hdr_sg;

	if (type & VIRTIO_BLK_T_OUT)
		sgs[num_out++] = &data_sg;
	else
		sgs[num_out + num_in++] = &data_sg;

	sgs[num_out + num_in++] = &status_sg;

	ret = virtqueue_add(priv->vq, sgs, num_out, num_in);
	if (ret)
		return ret;

	virtqueue_kick(priv->vq);

	while (!virtqueue_get_buf(priv->vq, NULL))
		;

	return status == VIRTIO_BLK_S_OK ? blkcnt : -EIO;
}

static ulong virtio_blk_read(struct udevice *dev, lbaint_t start,
			     lbaint_t blkcnt, void *buffer)
{
	return virtio_blk_do_req(dev, start, blkcnt, buffer,
				 VIRTIO_BLK_T_IN);
}

static ulong virtio_blk_write(struct udevice *dev, lbaint_t start,
			      lbaint_t blkcnt, const void *buffer)
{
	return virtio_blk_do_req(dev, start, blkcnt, (void *)buffer,
				 VIRTIO_BLK_T_OUT);
}

static int virtio_blk_bind(struct udevice *dev)
{
	struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
	struct blk_desc *desc = dev_get_uclass_platdata(dev);
	int devnum;

	desc->if_type = IF_TYPE_VIRTIO;
	/*
	 * Initialize the devnum to -ENODEV. This is to make sure that
	 * blk_next_free_devnum() works as expected, since the default
	 * value 0 is a valid devnum.
	 */
	desc->devnum = -ENODEV;
	devnum = blk_next_free_devnum(IF_TYPE_VIRTIO);
	if (devnum < 0)
		return devnum;
	desc->devnum = devnum;
	desc->part_type = PART_TYPE_UNKNOWN;
	/*
	 * virtio mmio transport supplies string identification for us,
	 * while pci trnasport uses a 2-byte subvendor value.
	 */
	if (uc_priv->vendor >> 16)
		sprintf(desc->vendor, "%s", (char *)&uc_priv->vendor);
	else
		sprintf(desc->vendor, "%04x", uc_priv->vendor);
	desc->bdev = dev;

	/* Indicate what driver features we support */
	virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0);

	return 0;
}

static int virtio_blk_probe(struct udevice *dev)
{
	struct virtio_blk_priv *priv = dev_get_priv(dev);
	struct blk_desc *desc = dev_get_uclass_platdata(dev);
	u64 cap;
	int ret;

	ret = virtio_find_vqs(dev, 1, &priv->vq);
	if (ret)
		return ret;

	desc->blksz = 512;
	virtio_cread(dev, struct virtio_blk_config, capacity, &cap);
	desc->lba = cap;

	return 0;
}

static const struct blk_ops virtio_blk_ops = {
	.read	= virtio_blk_read,
	.write	= virtio_blk_write,
};

U_BOOT_DRIVER(virtio_blk) = {
	.name	= VIRTIO_BLK_DRV_NAME,
	.id	= UCLASS_BLK,
	.ops	= &virtio_blk_ops,
	.bind	= virtio_blk_bind,
	.probe	= virtio_blk_probe,
	.remove	= virtio_reset,
	.priv_auto_alloc_size = sizeof(struct virtio_blk_priv),
	.flags	= DM_FLAG_ACTIVE_DMA,
};