--- 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:28 1.1.1.2 @@ -11,16 +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 {}; // ステータスバイトのアドレス (ゲストアドレス) -}; - // デバイス構成レイアウト class VirtIOBlkConfigWriter { @@ -53,24 +46,24 @@ 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)); - - // 割り込み名 - snprintf(intrname, sizeof(intrname), "VIOBlk%u", id); - // 完了通知メッセージ - msgid = MessageID::VIRTIO_BLOCK_DONE(id); + AddAlias(string_format("vblk%u", id)); device_id = VirtIO::DEVICE_ID_BLOCK; int num_max = 32; vqueues.emplace_back(0, "RequestQ", num_max); blocksize = 512; + // 割り込み名 + snprintf(intrname, sizeof(intrname), "VIOBlk%u", id); + // 完了通知メッセージ + msgid = MessageID::VIRTIO_BLOCK_DONE(id); + monitor.func = ToMonitorCallback(&VirtIOBlockDevice::MonitorUpdate); monitor.SetSize(MONITOR_WIDTH, 2 + 4 * 1 + num_max + 3); monitor.Regist(ID_MONITOR_VIRTIO_BLOCK(id)); @@ -89,8 +82,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,25 +118,18 @@ 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) { + w_ok = image.IsWriteable(); + if (w_ok < 0) { 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); - return false; - } - // 最大サイズの制約ある? - // 書き込みモードをここで確定 if (w_ok) { if (write_ignore) { @@ -161,6 +147,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 +181,139 @@ 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 (req.len=$%x)", req.len); + return; + } + ReqReadLE32(req); // skip + if (ReqReadLE64(req, §or) == false) { + putlog(0, "Cannot read sector in header (req.len=$%x)", req.len); + 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.len >= 16 + 1) { + datalen = req.len - (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; + uint64 imgoffset = sector * 512; - for (const auto seg : req.buf) { - std::vector tmpbuf(seg.len); + while (datalen != 0) { + if (req.curseg >= req.buf.size()) { + return VIRTIO_BLK_S_IOERR; + } + const auto seg = req.buf[req.curseg]; + uint32 segoff = req.pos - seg.pos; + std::vector databuf(seg.len - segoff); - if (image.Read(tmpbuf.data(), offset, tmpbuf.size()) == false) { + if (image.Read(databuf.data(), imgoffset, databuf.size()) == false) { return VIRTIO_BLK_S_IOERR; } - offset += tmpbuf.size(); + imgoffset += databuf.size(); - uint32 addr = seg.addr; - for (auto s : tmpbuf) { - WriteU8(addr++, s); + // 指定範囲は通常全域 RAM のはずなので mainram を直接呼ぶ。 + // 指定範囲が RAM からはみ出たりしていたら (通常まずありえない) + // mainbus 経由でアクセスする。 + uint32 addr = seg.addr + segoff; + bool ok = mainram->WriteMem(addr, databuf.data(), databuf.size()); + if (__predict_false(ok == false)) { + for (auto s : databuf) { + WriteU8(addr++, s); + } } + // 現在位置は必ずセグメント境界のはず。 + req.curseg++; + req.pos += databuf.size(); + + datalen -= databuf.size(); } return VIRTIO_BLK_S_OK; } // 書き込みコマンド (BLK_T_OUT) 実行。 uint32 -VirtIOBlockDevice::CmdWrite(VirtIOBlockReq& req) +VirtIOBlockDevice::CmdWrite(VirtIOReq& req, uint64 sector, uint32 datalen) { - uint64 offset = req.sector * 512; + uint64 imgoffset = 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) { + while (datalen != 0) { + if (req.curseg >= req.buf.size()) { return VIRTIO_BLK_S_IOERR; } - offset += tmpbuf.size(); - } - - return VIRTIO_BLK_S_OK; -} - -// リクエストを取り込む。 -void -VirtIOBlockDevice::ReadVirtIOBlockReq(VirtIOBlockReq& req) -{ - // 仕様的には任意のサイズで分割していいので、本当は1バイトずつバイト数 - // 数えながらバッファを再構築しないといけないはずだが、 - // 実際問題そんなことするゲストドライバもおらんだろう…。 - - 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 (__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; - } - - // 2チャンク目以降はデータセグメントが任意個連続していて、 - // 最後がステータスバイト。 - for (;;) { - desc_idx = desc.next; - reqlen += ReadDesc(q, &desc, desc_idx); - if (desc.IsNext() == false) { - break; + const auto seg = req.buf[req.curseg]; + uint32 segoff = req.pos - seg.pos; + std::vector databuf(seg.len - segoff); + + uint8 *d = &databuf[0]; + uint32 addr = seg.addr + segoff; + // 指定範囲は通常全域 RAM のはずなので mainram を直接呼ぶ。 + // 指定範囲が RAM からはみ出たりしていたら (通常まずありえない) + // mainbus 経由でアクセスする。 + bool ok = mainram->ReadMem(addr, databuf.data(), databuf.size()); + if (__predict_false(ok == false)) { + for (uint32 end = addr + databuf.size(); addr < end; ) { + *d++ = ReadU8(addr++); + } } + // 現在位置は必ずセグメント境界のはず。 + req.curseg++; + req.pos += databuf.size(); - // 最終チャンクでないので全部データ。 - AddDataSegment(&req, desc.addr, desc.len, NULL); - } + if (image.Write(databuf.data(), imgoffset, databuf.size()) == false) { + return VIRTIO_BLK_S_IOERR; + } + imgoffset += databuf.size(); - // 最終チャンク。 - 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; + datalen -= databuf.size(); } - 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 +342,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); }