|
|
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.