|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
//
// VirtIO ブロックデバイス
//
#include "virtio_block.h"
#include "virtio_def.h"
#include "config.h"
#include "mainram.h"
#include "memorystream.h"
#include "monitor.h"
// デバイス構成レイアウト
class VirtIOBlkConfigWriter
{
public:
le64 capacity {};
le32 size_max {};
le32 seg_max {};
struct {
le16 cylinders {};
u8 heads {};
u8 sectors {};
} geometry;
le32 blk_size {};
struct {
u8 physical_block_exp {};
u8 alignment_offset {};
le16 min_io_size {};
le32 opt_io_size {};
} topology;
u8 writeback {};
le32 max_discard_sectors {};
le32 max_discard_seg {};
le32 discard_sector_alignment {};
le32 max_write_zeroes_sectors {};
le32 max_write_zeroes_seg {};
u8 write_zeroes_may_unmap {};
public:
void WriteTo(uint8 *dst) const;
};
// コンストラクタ
VirtIOBlockDevice::VirtIOBlockDevice(uint slot_, uint id_)
: inherited(OBJ_VIRTIO_BLOCK(id_), slot_)
{
id = id_;
// 割り込み名(とログ名)
snprintf(intrname, sizeof(intrname), "VIOBlk%u", id);
SetName(intrname);
ClearAlias();
AddAlias(intrname);
image.SetName(intrname);
device_id = VirtIO::DEVICE_ID_BLOCK;
int num_max = 32;
vqueues.emplace_back(this, 0, "RequestQ", num_max);
blocksize = 512;
// 完了通知メッセージ
msgid = MessageID::VIRTIO_BLOCK_DONE(id);
monitor = gMonitorManager->Regist(ID_MONITOR_VIRTIO_BLOCK(id), this);
monitor->SetCallback(&VirtIOBlockDevice::MonitorScreen);
monitor->SetSize(MONITOR_WIDTH, 2 + 4 * 1 + num_max + 3);
}
// デストラクタ
VirtIOBlockDevice::~VirtIOBlockDevice()
{
}
// 初期化
bool
VirtIOBlockDevice::Init()
{
if (inherited::Init() == false) {
return false;
}
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).AsBool();
// オープン (値が空ならここには来ない)
const ConfigItem& itemimg = gConfig->Find(imgkey);
assert(itemimg.AsString().empty() == false);
std::string path = gMainApp.NormalizePath(itemimg.AsString());
if (LoadDisk(path) == false) {
itemimg.Err("Could not open %s", path.c_str());
return false;
}
uint seg_max = vqueues[0].num_max - 2;
// DEVICE_FEATURES と構成レイアウトを用意。
VirtIOBlkConfigWriter cfg;
SetDeviceFeatures(VIRTIO_BLK_F_SEG_MAX), cfg.seg_max = seg_max;
SetDeviceFeatures(VIRTIO_BLK_F_BLK_SIZE), cfg.blk_size = blocksize;
if (GetWriteMode() >= SCSIDisk::RW::WriteProtect) {
SetDeviceFeatures(VIRTIO_BLK_F_RO);
}
cfg.capacity = image.GetSize() / 512; // capacity は 512 バイト単位
cfg.WriteTo(&device_config[0]);
return true;
}
// バックエンドのディスクイメージを開く。
// 成功すれば true、失敗すれば false を返す。
bool
VirtIOBlockDevice::LoadDisk(const std::string& pathname_)
{
off_t size;
int w_ok;
pathname = pathname_;
if (image.CreateHandler(pathname) == false) {
return false;
}
w_ok = image.IsWriteable();
if (w_ok < 0) {
return false;
}
// 書き込みモードをここで確定
if (w_ok) {
if (write_ignore) {
write_mode = SCSIDisk::RW::WriteIgnore;
} else {
write_mode = SCSIDisk::RW::Writeable;
}
} else {
write_mode = SCSIDisk::RW::WriteProtect;
}
// ここでオープン
bool read_only = (GetWriteMode() != SCSIDisk::RW::Writeable);
if (image.Open(read_only, write_ignore) == false) {
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");
}
return true;
}
void
VirtIOBlockDevice::MonitorScreen(Monitor *, TextScreen& screen)
{
int y = 0;
screen.Clear();
y = MonitorScreenDev(screen, y);
y++;
y = MonitorScreenVirtQueue(screen, y, vqueues[0]);
y++;
y = MonitorScreenVirtQDesc(screen, y, vqueues[0]);
}
// ディスクリプタを一つ処理する。
void
VirtIOBlockDevice::ProcessDesc(VirtIOReq& req)
{
VirtQueue *q = req.q;
uint32 type;
uint64 sector;
uint32 status;
// ヘッダ部は 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, sector, datalen);
access_read = q->last_avail_idx;
break;
case VIRTIO_BLK_T_OUT:
status = CmdWrite(req, sector, datalen);
access_write = q->last_avail_idx;
break;
case VIRTIO_BLK_T_GET_ID:
status = CmdGetID(req, datalen);
break;
default:
putlog(0, "%s req.type=%u (NOT IMPLEMENTED)", __func__, type);
status = VIRTIO_BLK_S_UNSUPP;
break;
}
ReqWriteU8(req, status);
}
// 読み込みコマンド (BLK_T_IN) 実行。
uint32
VirtIOBlockDevice::CmdRead(VirtIOReq& req, uint64 sector, uint32 datalen)
{
std::vector<uint8> databuf(datalen);
if (image.Read(databuf.data(), sector * 512, databuf.size()) == false) {
return VIRTIO_BLK_S_IOERR;
}
if (ReqWriteRegion(req, databuf.data(), datalen) != 0) {
return VIRTIO_BLK_S_IOERR;
}
return VIRTIO_BLK_S_OK;
}
// 書き込みコマンド (BLK_T_OUT) 実行。
uint32
VirtIOBlockDevice::CmdWrite(VirtIOReq& req, uint64 sector, uint32 datalen)
{
std::vector<uint8> databuf(datalen);
if (ReqReadRegion(req, databuf.data(), datalen) != 0) {
return VIRTIO_BLK_S_IOERR;
}
if (image.Write(databuf.data(), sector * 512, databuf.size()) == false) {
return VIRTIO_BLK_S_IOERR;
}
return VIRTIO_BLK_S_OK;
}
// ID 取得コマンド (BLK_T_GET_ID) 実行。
uint32
VirtIOBlockDevice::CmdGetID(VirtIOReq& req, uint32 datalen)
{
std::vector<uint8> databuf(datalen);
// Device ID string を返すらしいが、どういう文字列か分からん。
// 20バイトで余りはゼロ埋め。20文字なら終端文字なしで 20文字有効。
snprintf((char *)databuf.data(), datalen, "virtio-block%u", id);
if (ReqWriteRegion(req, databuf.data(), datalen) != 0) {
return VIRTIO_BLK_S_IOERR;
}
return VIRTIO_BLK_S_OK;
}
const char *
VirtIOBlockDevice::GetFeatureName(uint feature) const
{
static std::pair<uint, const char *> names[] = {
{ VIRTIO_BLK_F_SIZE_MAX, "SIZE_MAX" },
{ VIRTIO_BLK_F_SEG_MAX, "SEG_MAX" },
{ VIRTIO_BLK_F_GEOMETRY, "GEOMETRY" },
{ VIRTIO_BLK_F_RO, "RO" },
{ VIRTIO_BLK_F_BLK_SIZE, "BLK_SIZE" },
{ VIRTIO_BLK_F_FLUSH, "FLUSH" },
{ VIRTIO_BLK_F_TOPOLOGY, "TOPOLOGY" },
{ VIRTIO_BLK_F_CONFIG_WCE, "CFG_WCE" },
{ VIRTIO_BLK_F_DISCARD, "DISCARD" },
{ VIRTIO_BLK_F_WRITE_ZEROES,"WZEROES" },
};
for (auto& p : names) {
if (feature == p.first) {
return p.second;
}
}
return inherited::GetFeatureName(feature);
}
// virtio_blk_config 構造体を書き出す。
void
VirtIOBlkConfigWriter::WriteTo(uint8 *dst) const
{
MemoryStreamLE mem(dst);
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);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.