--- nono/vm/virtio_block.cpp 2026/04/29 17:05:24 1.1.1.1 +++ nono/vm/virtio_block.cpp 2026/04/29 17:05:42 1.1.1.4 @@ -11,15 +11,9 @@ #include "virtio_block.h" #include "virtio_def.h" #include "config.h" +#include "mainram.h" #include "memorystream.h" - -class VirtIOBlockReq : public VirtIOReq -{ - public: - uint32 type {}; // VIRTIO_BLK_T_* - uint64 sector {}; // 開始セクタ (512 バイト単位のセクタ番号) - uint32 statusaddr {}; // ステータスバイトのアドレス (ゲストアドレス) -}; +#include "monitor.h" // デバイス構成レイアウト class VirtIOBlkConfigWriter @@ -53,27 +47,27 @@ class VirtIOBlkConfigWriter }; // コンストラクタ -VirtIOBlockDevice::VirtIOBlockDevice(int slot_, int id_) +VirtIOBlockDevice::VirtIOBlockDevice(uint slot_, uint id_) : inherited(OBJ_VIRTIO_BLOCK(id_), slot_) { id = id_; // 短縮形 - AddAlias(string_format("vblk%d", id)); + AddAlias(string_format("vblk%u", id)); + + device_id = VirtIO::DEVICE_ID_BLOCK; + int num_max = 32; + vqueues.emplace_back(this, 0, "RequestQ", num_max); + blocksize = 512; // 割り込み名 snprintf(intrname, sizeof(intrname), "VIOBlk%u", id); // 完了通知メッセージ msgid = MessageID::VIRTIO_BLOCK_DONE(id); - device_id = VirtIO::DEVICE_ID_BLOCK; - int num_max = 32; - vqueues.emplace_back(0, "RequestQ", num_max); - blocksize = 512; - - monitor.func = ToMonitorCallback(&VirtIOBlockDevice::MonitorUpdate); - monitor.SetSize(MONITOR_WIDTH, 2 + 4 * 1 + num_max + 3); - monitor.Regist(ID_MONITOR_VIRTIO_BLOCK(id)); + monitor = gMonitorManager->Regist(ID_MONITOR_VIRTIO_BLOCK(id), this); + monitor->func = ToMonitorCallback(&VirtIOBlockDevice::MonitorUpdate); + monitor->SetSize(MONITOR_WIDTH, 2 + 4 * 1 + num_max + 3); } // デストラクタ @@ -89,8 +83,8 @@ VirtIOBlockDevice::Init() return false; } - const std::string imgkey = string_format("virtio-block%d-image", id); - const std::string wikey = string_format("virtio-block%d-writeignore", id); + const std::string imgkey = string_format("virtio-block%u-image", id); + const std::string wikey = string_format("virtio-block%u-writeignore", id); // オープン前に writeignore をチェック。see scsidev.cpp write_ignore = gConfig->Find(wikey).AsInt(); @@ -125,24 +119,17 @@ bool VirtIOBlockDevice::LoadDisk(const std::string& pathname_) { off_t size; - bool w_ok; + int w_ok; pathname = pathname_; if (image.CreateHandler(pathname) == false) { return false; } - if (image.GetInfo(&size, &w_ok) == false) { - return false; - } - - // セクタ単位になっていること - if (size % blocksize != 0) { - warnx("%s: Bad image size(%ju): Not a multiple of blocksize(%d)", - pathname.c_str(), (uintmax_t)size, blocksize); + w_ok = image.IsWriteable(); + if (w_ok < 0) { return false; } - // 最大サイズの制約ある? // 書き込みモードをここで確定 if (w_ok) { @@ -161,6 +148,16 @@ VirtIOBlockDevice::LoadDisk(const std::s return false; } + // セクタ単位になっていること + size = image.GetSize(); + if (size % blocksize != 0) { + warnx("%s: Bad image size(%ju): Not a multiple of blocksize(%u)", + pathname.c_str(), (uintmax_t)size, blocksize); + image.Close(); + return false; + } + // 最大サイズの制約ある? + // 書き込み無視なら一応ログ出力 if (GetWriteMode() == SCSIDisk::RW::WriteIgnore) { putmsg(0, "write is ignored"); @@ -185,157 +182,93 @@ VirtIOBlockDevice::MonitorUpdate(Monitor // ディスクリプタを一つ処理する。 void -VirtIOBlockDevice::ProcessDesc(VirtQueue *q) +VirtIOBlockDevice::ProcessDesc(VirtIOReq& req) { - VirtIOBlockReq req; + VirtQueue *q = req.q; + uint32 type; + uint64 sector; uint32 status; - req.q = q; - ReadVirtIOBlockReq(req); - switch (req.type) { + // ヘッダ部は 16 バイト。 + // +0.L: type (コマンド) + // +4.L: (reserved) + // +8.L: 開始セクタ番号 + + if (ReqReadLE32(req, &type) == false) { + putlog(0, "Cannot read type in header"); + return; + } + ReqReadLE32(req); // skip + if (ReqReadLE64(req, §or) == false) { + putlog(0, "Cannot read sector in header"); + return; + } + putlog(3, "%s req.idx=%u type=%x sec=$%x'%08x", + __func__, req.idx, type, (uint32)(sector >> 32), (uint32)sector); + + // 転送データ長は、ディスクリプタの全長から + // ヘッダ(16バイト) とステータスバイト(1バイト) を引いた部分。 + // という求め方しかない。 + uint32 datalen; + if (req.totallen() >= 16 + 1) { + datalen = req.totallen() - (16 + 1); + } else { + datalen = 0; + } + + switch (type) { case VIRTIO_BLK_T_IN: - status = CmdRead(req); + status = CmdRead(req, sector, datalen); access_read = q->last_avail_idx; break; case VIRTIO_BLK_T_OUT: - status = CmdWrite(req); + status = CmdWrite(req, sector, datalen); access_write = q->last_avail_idx; break; default: - putlog(0, "QueueNotify req.type=%u (NOT IMPLEMENTED)", req.type); + putlog(0, "%s req.type=%u (NOT IMPLEMENTED)", __func__, type); status = VIRTIO_BLK_S_UNSUPP; break; } - WriteU8(req.statusaddr, status); - - CommitDesc(q, req.idx, req.len); + ReqWriteU8(req, status); } // 読み込みコマンド (BLK_T_IN) 実行。 uint32 -VirtIOBlockDevice::CmdRead(VirtIOBlockReq& req) +VirtIOBlockDevice::CmdRead(VirtIOReq& req, uint64 sector, uint32 datalen) { - uint64 offset = req.sector * 512; + std::vector databuf(datalen); - for (const auto seg : req.buf) { - std::vector tmpbuf(seg.len); - - if (image.Read(tmpbuf.data(), offset, tmpbuf.size()) == false) { - return VIRTIO_BLK_S_IOERR; - } - offset += tmpbuf.size(); - - uint32 addr = seg.addr; - for (auto s : tmpbuf) { - WriteU8(addr++, s); - } + if (image.Read(databuf.data(), sector * 512, databuf.size()) == false) { + return VIRTIO_BLK_S_IOERR; } - return VIRTIO_BLK_S_OK; -} - -// 書き込みコマンド (BLK_T_OUT) 実行。 -uint32 -VirtIOBlockDevice::CmdWrite(VirtIOBlockReq& req) -{ - uint64 offset = req.sector * 512; - - for (const auto seg : req.buf) { - std::vector tmpbuf(seg.len); - uint8 *d = &tmpbuf[0]; - uint32 addr = seg.addr; - for (uint32 end = addr + seg.len; addr < end; ) { - *d++ = ReadU8(addr++); - } - - if (image.Write(tmpbuf.data(), offset, tmpbuf.size()) == false) { - return VIRTIO_BLK_S_IOERR; - } - offset += tmpbuf.size(); + if (ReqWriteRegion(req, databuf.data(), datalen) != 0) { + return VIRTIO_BLK_S_IOERR; } return VIRTIO_BLK_S_OK; } -// リクエストを取り込む。 -void -VirtIOBlockDevice::ReadVirtIOBlockReq(VirtIOBlockReq& req) +// 書き込みコマンド (BLK_T_OUT) 実行。 +uint32 +VirtIOBlockDevice::CmdWrite(VirtIOReq& req, uint64 sector, uint32 datalen) { - // 仕様的には任意のサイズで分割していいので、本当は1バイトずつバイト数 - // 数えながらバッファを再構築しないといけないはずだが、 - // 実際問題そんなことするゲストドライバもおらんだろう…。 + std::vector databuf(datalen); - VirtQueue *q = req.q; - VirtQDesc desc; - uint32 desc_idx; - uint32 reqlen = 0; - - // 1チャンク目は 16 バイトで Read-Only なヘッダのはず。 - desc_idx = ReadLE16(q->driver + 4 + (q->last_avail_idx % q->num) * 2); - reqlen += ReadDesc(q, &desc, desc_idx); - if (__predict_false(desc.IsRead() == false)) { - putlog(0, "Header segment must be device-readable"); - return; + if (ReqReadRegion(req, databuf.data(), datalen) != 0) { + return VIRTIO_BLK_S_IOERR; } - if (__predict_false(desc.len < 16)) { - putlog(0, "Header too short (desc.len=$%x)", desc.len); - return; - } - req.idx = desc_idx; - req.type = ReadLE32(desc.addr + 0); - req.sector = ReadLE64(desc.addr + 8); - putlog(3, "%s req.idx=%u type=%x sec=%x", - __func__, req.idx, req.type, (uint32)req.sector); - - // 仕様上は、T_OUT、T_DISCARD、T_WRITE_ZEROES コマンド時は - // 同一セグメント内で、ヘッダ(ReadOnly)とデータ(ReadOnly)が連続していても - // 構わないはず…。誰もやらないとは思うけど…。 - if (__predict_false(desc.len > 16)) { - AddDataSegment(&req, desc.addr + 16, desc.len - 16, - " (header segment)"); - } - - // 最低でも2チャンクはあるので継続のはず。 - if (__predict_false(desc.IsNext() == false)) { - putlog(0, "Subsequent buffer not found?"); - return; + if (image.Write(databuf.data(), sector * 512, databuf.size()) == false) { + return VIRTIO_BLK_S_IOERR; } - // 2チャンク目以降はデータセグメントが任意個連続していて、 - // 最後がステータスバイト。 - for (;;) { - desc_idx = desc.next; - reqlen += ReadDesc(q, &desc, desc_idx); - if (desc.IsNext() == false) { - break; - } - - // 最終チャンクでないので全部データ。 - AddDataSegment(&req, desc.addr, desc.len, NULL); - } - - // 最終チャンク。 - if (desc.IsWrite() == false) { - putlog(0, "Last segment must be device-writeable"); - return; - } - // 仕様上は、T_IN コマンド時は同一セグメント内にデータ(WriteOnly)と - // ステータス(WriteOnly) が連続していても構わないはず…。 - // 誰もやらないとは思うけど…。 - uint32 addr = desc.addr; - if (__predict_false(desc.len > 1)) { - AddDataSegment(&req, addr, desc.len - 1, " (status segment)"); - addr += desc.len - 1; - } - - req.len = reqlen; - req.statusaddr = addr; - putlog(3, "req.statusaddr=%08x len=%08x", req.statusaddr, req.len); + return VIRTIO_BLK_S_OK; } const char * -VirtIOBlockDevice::GetFeatureName(int feature) const +VirtIOBlockDevice::GetFeatureName(uint feature) const { static std::pair names[] = { { VIRTIO_BLK_F_SIZE_MAX, "SIZE_MAX" }, @@ -364,28 +297,28 @@ VirtIOBlkConfigWriter::WriteTo(uint8 *ds { MemoryStreamLE mem(dst); - mem.Write64(capacity); - mem.Write32(size_max); - mem.Write32(seg_max); - mem.Write16(geometry.cylinders); - mem.Write8(geometry.heads); - mem.Write8(geometry.sectors); - mem.Write32(blk_size); - mem.Write8(topology.physical_block_exp); - mem.Write8(topology.alignment_offset); - mem.Write16(topology.min_io_size); - mem.Write32(topology.opt_io_size); - mem.Write8(writeback); - mem.Write8(0); - mem.Write8(0); - mem.Write8(0); - mem.Write32(max_discard_sectors); - mem.Write32(max_discard_seg); - mem.Write32(discard_sector_alignment); - mem.Write32(max_write_zeroes_sectors); - mem.Write32(max_write_zeroes_seg); - mem.Write8(write_zeroes_may_unmap); - mem.Write8(0); - mem.Write8(0); - mem.Write8(0); + mem.Write8(capacity); + mem.Write4(size_max); + mem.Write4(seg_max); + mem.Write2(geometry.cylinders); + mem.Write1(geometry.heads); + mem.Write1(geometry.sectors); + mem.Write4(blk_size); + mem.Write1(topology.physical_block_exp); + mem.Write1(topology.alignment_offset); + mem.Write2(topology.min_io_size); + mem.Write4(topology.opt_io_size); + mem.Write1(writeback); + mem.Write1(0); + mem.Write1(0); + mem.Write1(0); + mem.Write4(max_discard_sectors); + mem.Write4(max_discard_seg); + mem.Write4(discard_sector_alignment); + mem.Write4(max_write_zeroes_sectors); + mem.Write4(max_write_zeroes_seg); + mem.Write1(write_zeroes_may_unmap); + mem.Write1(0); + mem.Write1(0); + mem.Write1(0); }