|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2024 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // VirtIO ブロックデバイス
9: //
10:
11: #include "virtio_block.h"
12: #include "virtio_def.h"
13: #include "config.h"
14: #include "memorystream.h"
15:
16: class VirtIOBlockReq : public VirtIOReq
17: {
18: public:
19: uint32 type {}; // VIRTIO_BLK_T_*
20: uint64 sector {}; // 開始セクタ (512 バイト単位のセクタ番号)
21: uint32 statusaddr {}; // ステータスバイトのアドレス (ゲストアドレス)
22: };
23:
24: // デバイス構成レイアウト
25: class VirtIOBlkConfigWriter
26: {
27: public:
28: le64 capacity {};
29: le32 size_max {};
30: le32 seg_max {};
31: struct {
32: le16 cylinders {};
33: u8 heads {};
34: u8 sectors {};
35: } geometry;
36: le32 blk_size {};
37: struct {
38: u8 physical_block_exp {};
39: u8 alignment_offset {};
40: le16 min_io_size {};
41: le32 opt_io_size {};
42: } topology;
43: u8 writeback {};
44: le32 max_discard_sectors {};
45: le32 max_discard_seg {};
46: le32 discard_sector_alignment {};
47: le32 max_write_zeroes_sectors {};
48: le32 max_write_zeroes_seg {};
49: u8 write_zeroes_may_unmap {};
50:
51: public:
52: void WriteTo(uint8 *dst) const;
53: };
54:
55: // コンストラクタ
56: VirtIOBlockDevice::VirtIOBlockDevice(int slot_, int id_)
57: : inherited(OBJ_VIRTIO_BLOCK(id_), slot_)
58: {
59: id = id_;
60:
61: // 短縮形
62: AddAlias(string_format("vblk%d", id));
63:
64: // 割り込み名
65: snprintf(intrname, sizeof(intrname), "VIOBlk%u", id);
66: // 完了通知メッセージ
67: msgid = MessageID::VIRTIO_BLOCK_DONE(id);
68:
69: device_id = VirtIO::DEVICE_ID_BLOCK;
70: int num_max = 32;
71: vqueues.emplace_back(0, "RequestQ", num_max);
72: blocksize = 512;
73:
74: monitor.func = ToMonitorCallback(&VirtIOBlockDevice::MonitorUpdate);
75: monitor.SetSize(MONITOR_WIDTH, 2 + 4 * 1 + num_max + 3);
76: monitor.Regist(ID_MONITOR_VIRTIO_BLOCK(id));
77: }
78:
79: // デストラクタ
80: VirtIOBlockDevice::~VirtIOBlockDevice()
81: {
82: }
83:
84: // 初期化
85: bool
86: VirtIOBlockDevice::Init()
87: {
88: if (inherited::Init() == false) {
89: return false;
90: }
91:
92: const std::string imgkey = string_format("virtio-block%d-image", id);
93: const std::string wikey = string_format("virtio-block%d-writeignore", id);
94:
95: // オープン前に writeignore をチェック。see scsidev.cpp
96: write_ignore = gConfig->Find(wikey).AsInt();
97:
98: // オープン (値が空ならここには来ない)
99: const ConfigItem& itemimg = gConfig->Find(imgkey);
100: assert(itemimg.AsString().empty() == false);
101: std::string path = gMainApp.NormalizePath(itemimg.AsString());
102: if (LoadDisk(path) == false) {
103: return false;
104: }
105:
106: uint seg_max = vqueues[0].num_max - 2;
107:
108: // DEVICE_FEATURES と構成レイアウトを用意。
109: VirtIOBlkConfigWriter cfg;
110: SetDeviceFeatures(VIRTIO_BLK_F_SEG_MAX), cfg.seg_max = seg_max;
111: SetDeviceFeatures(VIRTIO_BLK_F_BLK_SIZE), cfg.blk_size = blocksize;
112: if (GetWriteMode() >= SCSIDisk::RW::WriteProtect) {
113: SetDeviceFeatures(VIRTIO_BLK_F_RO);
114: }
115:
116: cfg.capacity = image.GetSize() / 512; // capacity は 512 バイト単位
117: cfg.WriteTo(&device_config[0]);
118:
119: return true;
120: }
121:
122: // バックエンドのディスクイメージを開く。
123: // 成功すれば true、失敗すれば false を返す。
124: bool
125: VirtIOBlockDevice::LoadDisk(const std::string& pathname_)
126: {
127: off_t size;
128: bool w_ok;
129:
130: pathname = pathname_;
131: if (image.CreateHandler(pathname) == false) {
132: return false;
133: }
134:
135: if (image.GetInfo(&size, &w_ok) == false) {
136: return false;
137: }
138:
139: // セクタ単位になっていること
140: if (size % blocksize != 0) {
141: warnx("%s: Bad image size(%ju): Not a multiple of blocksize(%d)",
142: pathname.c_str(), (uintmax_t)size, blocksize);
143: return false;
144: }
145: // 最大サイズの制約ある?
146:
147: // 書き込みモードをここで確定
148: if (w_ok) {
149: if (write_ignore) {
150: write_mode = SCSIDisk::RW::WriteIgnore;
151: } else {
152: write_mode = SCSIDisk::RW::Writeable;
153: }
154: } else {
155: write_mode = SCSIDisk::RW::WriteProtect;
156: }
157:
158: // ここでオープン
159: bool read_only = (GetWriteMode() != SCSIDisk::RW::Writeable);
160: if (image.Open(read_only, write_ignore) == false) {
161: return false;
162: }
163:
164: // 書き込み無視なら一応ログ出力
165: if (GetWriteMode() == SCSIDisk::RW::WriteIgnore) {
166: putmsg(0, "write is ignored");
167: }
168:
169: return true;
170: }
171:
172: void
173: VirtIOBlockDevice::MonitorUpdate(Monitor *, TextScreen& screen)
174: {
175: int y = 0;
176:
177: screen.Clear();
178:
179: y = MonitorUpdateDev(screen, y);
180: y++;
181: y = MonitorUpdateVirtQueue(screen, y, vqueues[0]);
182: y++;
183: y = MonitorUpdateVirtQDesc(screen, y, vqueues[0]);
184: }
185:
186: // ディスクリプタを一つ処理する。
187: void
188: VirtIOBlockDevice::ProcessDesc(VirtQueue *q)
189: {
190: VirtIOBlockReq req;
191: uint32 status;
192:
193: req.q = q;
194: ReadVirtIOBlockReq(req);
195: switch (req.type) {
196: case VIRTIO_BLK_T_IN:
197: status = CmdRead(req);
198: access_read = q->last_avail_idx;
199: break;
200:
201: case VIRTIO_BLK_T_OUT:
202: status = CmdWrite(req);
203: access_write = q->last_avail_idx;
204: break;
205:
206: default:
207: putlog(0, "QueueNotify req.type=%u (NOT IMPLEMENTED)", req.type);
208: status = VIRTIO_BLK_S_UNSUPP;
209: break;
210: }
211: WriteU8(req.statusaddr, status);
212:
213: CommitDesc(q, req.idx, req.len);
214: }
215:
216: // 読み込みコマンド (BLK_T_IN) 実行。
217: uint32
218: VirtIOBlockDevice::CmdRead(VirtIOBlockReq& req)
219: {
220: uint64 offset = req.sector * 512;
221:
222: for (const auto seg : req.buf) {
223: std::vector<uint8> tmpbuf(seg.len);
224:
225: if (image.Read(tmpbuf.data(), offset, tmpbuf.size()) == false) {
226: return VIRTIO_BLK_S_IOERR;
227: }
228: offset += tmpbuf.size();
229:
230: uint32 addr = seg.addr;
231: for (auto s : tmpbuf) {
232: WriteU8(addr++, s);
233: }
234: }
235: return VIRTIO_BLK_S_OK;
236: }
237:
238: // 書き込みコマンド (BLK_T_OUT) 実行。
239: uint32
240: VirtIOBlockDevice::CmdWrite(VirtIOBlockReq& req)
241: {
242: uint64 offset = req.sector * 512;
243:
244: for (const auto seg : req.buf) {
245: std::vector<uint8> tmpbuf(seg.len);
246: uint8 *d = &tmpbuf[0];
247: uint32 addr = seg.addr;
248: for (uint32 end = addr + seg.len; addr < end; ) {
249: *d++ = ReadU8(addr++);
250: }
251:
252: if (image.Write(tmpbuf.data(), offset, tmpbuf.size()) == false) {
253: return VIRTIO_BLK_S_IOERR;
254: }
255: offset += tmpbuf.size();
256: }
257:
258: return VIRTIO_BLK_S_OK;
259: }
260:
261: // リクエストを取り込む。
262: void
263: VirtIOBlockDevice::ReadVirtIOBlockReq(VirtIOBlockReq& req)
264: {
265: // 仕様的には任意のサイズで分割していいので、本当は1バイトずつバイト数
266: // 数えながらバッファを再構築しないといけないはずだが、
267: // 実際問題そんなことするゲストドライバもおらんだろう…。
268:
269: VirtQueue *q = req.q;
270: VirtQDesc desc;
271: uint32 desc_idx;
272: uint32 reqlen = 0;
273:
274: // 1チャンク目は 16 バイトで Read-Only なヘッダのはず。
275: desc_idx = ReadLE16(q->driver + 4 + (q->last_avail_idx % q->num) * 2);
276: reqlen += ReadDesc(q, &desc, desc_idx);
277: if (__predict_false(desc.IsRead() == false)) {
278: putlog(0, "Header segment must be device-readable");
279: return;
280: }
281: if (__predict_false(desc.len < 16)) {
282: putlog(0, "Header too short (desc.len=$%x)", desc.len);
283: return;
284: }
285: req.idx = desc_idx;
286: req.type = ReadLE32(desc.addr + 0);
287: req.sector = ReadLE64(desc.addr + 8);
288: putlog(3, "%s req.idx=%u type=%x sec=%x",
289: __func__, req.idx, req.type, (uint32)req.sector);
290:
291: // 仕様上は、T_OUT、T_DISCARD、T_WRITE_ZEROES コマンド時は
292: // 同一セグメント内で、ヘッダ(ReadOnly)とデータ(ReadOnly)が連続していても
293: // 構わないはず…。誰もやらないとは思うけど…。
294: if (__predict_false(desc.len > 16)) {
295: AddDataSegment(&req, desc.addr + 16, desc.len - 16,
296: " (header segment)");
297: }
298:
299: // 最低でも2チャンクはあるので継続のはず。
300: if (__predict_false(desc.IsNext() == false)) {
301: putlog(0, "Subsequent buffer not found?");
302: return;
303: }
304:
305: // 2チャンク目以降はデータセグメントが任意個連続していて、
306: // 最後がステータスバイト。
307: for (;;) {
308: desc_idx = desc.next;
309: reqlen += ReadDesc(q, &desc, desc_idx);
310: if (desc.IsNext() == false) {
311: break;
312: }
313:
314: // 最終チャンクでないので全部データ。
315: AddDataSegment(&req, desc.addr, desc.len, NULL);
316: }
317:
318: // 最終チャンク。
319: if (desc.IsWrite() == false) {
320: putlog(0, "Last segment must be device-writeable");
321: return;
322: }
323: // 仕様上は、T_IN コマンド時は同一セグメント内にデータ(WriteOnly)と
324: // ステータス(WriteOnly) が連続していても構わないはず…。
325: // 誰もやらないとは思うけど…。
326: uint32 addr = desc.addr;
327: if (__predict_false(desc.len > 1)) {
328: AddDataSegment(&req, addr, desc.len - 1, " (status segment)");
329: addr += desc.len - 1;
330: }
331:
332: req.len = reqlen;
333: req.statusaddr = addr;
334: putlog(3, "req.statusaddr=%08x len=%08x", req.statusaddr, req.len);
335: }
336:
337: const char *
338: VirtIOBlockDevice::GetFeatureName(int feature) const
339: {
340: static std::pair<uint, const char *> names[] = {
341: { VIRTIO_BLK_F_SIZE_MAX, "SIZE_MAX" },
342: { VIRTIO_BLK_F_SEG_MAX, "SEG_MAX" },
343: { VIRTIO_BLK_F_GEOMETRY, "GEOMETRY" },
344: { VIRTIO_BLK_F_RO, "RO" },
345: { VIRTIO_BLK_F_BLK_SIZE, "BLK_SIZE" },
346: { VIRTIO_BLK_F_FLUSH, "FLUSH" },
347: { VIRTIO_BLK_F_TOPOLOGY, "TOPOLOGY" },
348: { VIRTIO_BLK_F_CONFIG_WCE, "CFG_WCE" },
349: { VIRTIO_BLK_F_DISCARD, "DISCARD" },
350: { VIRTIO_BLK_F_WRITE_ZEROES,"WZEROES" },
351: };
352:
353: for (auto& p : names) {
354: if (feature == p.first) {
355: return p.second;
356: }
357: }
358: return inherited::GetFeatureName(feature);
359: }
360:
361: // virtio_blk_config 構造体を書き出す。
362: void
363: VirtIOBlkConfigWriter::WriteTo(uint8 *dst) const
364: {
365: MemoryStreamLE mem(dst);
366:
367: mem.Write64(capacity);
368: mem.Write32(size_max);
369: mem.Write32(seg_max);
370: mem.Write16(geometry.cylinders);
371: mem.Write8(geometry.heads);
372: mem.Write8(geometry.sectors);
373: mem.Write32(blk_size);
374: mem.Write8(topology.physical_block_exp);
375: mem.Write8(topology.alignment_offset);
376: mem.Write16(topology.min_io_size);
377: mem.Write32(topology.opt_io_size);
378: mem.Write8(writeback);
379: mem.Write8(0);
380: mem.Write8(0);
381: mem.Write8(0);
382: mem.Write32(max_discard_sectors);
383: mem.Write32(max_discard_seg);
384: mem.Write32(discard_sector_alignment);
385: mem.Write32(max_write_zeroes_sectors);
386: mem.Write32(max_write_zeroes_seg);
387: mem.Write8(write_zeroes_may_unmap);
388: mem.Write8(0);
389: mem.Write8(0);
390: mem.Write8(0);
391: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.