|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
//
// VirtIO
//
#pragma once
#include "device.h"
#include "message.h"
#include "missing_bswap.h"
#include "thread.h"
#include <array>
#include <condition_variable>
class InterruptDevice;
class MainbusDevice;
class MainRAMDevice;
class TextScreen;
class VirtIODevice;
class VirtIOThread;
class VirtQDesc;
class VirtQueue
{
public:
VirtQueue(VirtIODevice *parent_, int idx_, const std::string& name_,
int num_max_);
// idx_ % num を返す。
// num が 0 なら何でもいいので死なないように 0 を返す。
uint Mod(uint idx_) const {
if (__predict_false(num == 0)) {
return 0;
}
return idx_ % num;
}
uint32 last_avail_idx {};
uint idx {}; // 自身が何番目か
std::string name {}; // キュー名(仕様から決まる)
uint32 desc {}; // QUEUE_DESC (下位32ビットのみ)
uint32 driver {}; // QUEUE_DRIVER (下位32ビットのみ)
uint32 device {}; // QUEUE_DEVICE (下位32ビットのみ)
uint num {}; // QUEUE_NUM (合意できたディスクリプタ数)
uint num_max {}; // QUEUE_NUM の上限
// QUEUE_READY の設定と取得。
// Get は副作用を起こしてはいけない。
void SetReady(uint32 val);
uint32 GetReady() const noexcept { return ready; }
private:
VirtIODevice *parent {};
uint32 ready {}; // QUEUE_READY
};
class VirtIOSeg
{
public:
uint32 addr {}; // セグメントアドレス (ゲストアドレス)
uint32 len {}; // このセグメント長
VirtIOSeg(uint32 addr_, uint32 len_)
: addr(addr_), len(len_)
{
}
};
// ディスクリプタチェイン。
// ファイルハンドルのようなものも兼ねている。
// 元の仕様からシーケンシャルアクセスを想定しているようなので、ここでも
// シーケンシャルアクセスを前提とする。そのため pos() が UsedRing に書き込む
// バイト数になる。
class VirtIOReq
{
public:
VirtQueue *q {};
uint32 idx {}; // このディスクリプタチェインの先頭インデックス
int rseg {}; // 現在の読み込みセグメントインデックス
uint32 roff {}; // 読み込み位置(セグメント内の相対位置)
std::vector<VirtIOSeg> rbuf {}; // 読み込み用セグメント
uint32 rlen {}; // 読み込みセグメントの総バイト数
int wseg {}; // 現在の書き込みセグメントインデックス
uint32 woff {}; // 書き込み位置(セグメント内の相対位置)
std::vector<VirtIOSeg> wbuf {}; // 書き込み用セグメント
uint32 wlen {}; // 書き込みセグメントの総バイト数
// このディスクリプタチェインの総バイト数。
uint32 totallen() const noexcept { return rlen + wlen; }
// 読み込みセグメントの残りバイト数。
uint32 rremain() const { return rlen - rpos(); }
// 読み込みセグメントの残りをすべて読み捨てる。
void rskip() {
rseg = rbuf.size();
roff = 0;
}
// 読み込みセグメントの先頭からの現在位置を返す。
uint32 rpos() const;
// 書き込みセグメントの先頭からの現在位置を返す。
uint32 wpos() const;
// 処理した総バイト数を返す。
uint32 pos() const { return rpos() + wpos(); }
// 読み込みまたは書き込みが末尾に到達していれば true を返す。
bool reof() const { return rseg >= rbuf.size(); }
bool weof() const { return wseg >= wbuf.size(); }
};
// VirtIO 基本クラス
class VirtIODevice : public IODevice
{
using inherited = IODevice;
friend class VirtQueue;
protected:
// 仕様書には名前が出てこないけど上限
static const uint MAX_FEATURES_SEL = 2;
// モニタ幅
static const int MONITOR_WIDTH = 75;
// 動作には不要だがモニタに表示したいので…。
static const uint32 baseaddr = 0xff070000;
public:
VirtIODevice(uint objid_, uint slot_);
~VirtIODevice() override;
bool Create() override;
void SetLogLevel(int loglevel_) override;
bool Init() override;
void ResetHard(bool poweron) override;
// QUEUE_NOTIFY の処理 (裏スレッドから呼ばれる)
void QueueNotify(uint idx);
protected:
// BusIO インタフェース
static const uint32 NPORT = 0x200 >> 2;
busdata ReadPort(uint32 offset);
busdata WritePort(uint32 offset, uint32 data);
busdata PeekPort(uint32 offset);
int MonitorScreenDev(TextScreen&, int y) const;
int MonitorScreenVirtQueue(TextScreen&, int y, const VirtQueue&) const;
int MonitorScreenVirtQDesc(TextScreen&, int y, const VirtQueue&) const;
void Reset();
virtual uint32 GetReg(uint32 addr) const;
virtual void QueueReadyChanged(VirtQueue *);
// ディスクリプタを一つ処理する。
virtual void ProcessDesc(VirtIOReq&) = 0;
// ディスクリプタを読んでチェインを用意する。
void StartDesc(VirtIOReq&);
// ディスクリプタを更新する。
void CommitDesc(VirtIOReq&);
// ディスクリプタアクセス
uint32 ReqReadU8(VirtIOReq&);
uint32 ReqReadLE16(VirtIOReq&);
uint64 ReqReadLE32(VirtIOReq&);
uint32 ReqReadBE16(VirtIOReq& req) { return bswap16(ReqReadLE16(req)); }
uint32 ReqReadBE32(VirtIOReq& req) { return bswap32(ReqReadLE32(req)); }
bool ReqReadLE32(VirtIOReq&, uint32 *);
bool ReqReadLE64(VirtIOReq&, uint64 *);
bool ReqWriteU8(VirtIOReq&, uint32 data);
bool ReqWriteLE16(VirtIOReq&, uint32 data);
bool ReqWriteLE32(VirtIOReq&, uint32 data);
uint32 ReqReadRegion(VirtIOReq&, uint8 *dst, uint32 dstlen);
uint32 ReqWriteRegion(VirtIOReq&, const uint8 *src, uint32 srclen);
// 裏スレッドからの処理完了メッセージ
void DoneMessage(MessageID, uint32);
// 処理完了
void Done(VirtQueue *);
void ChangeInterrupt();
// メインバスアクセス
uint32 ReadU8(uint32 addr);
uint32 ReadLE16(uint32 addr);
uint32 ReadLE32(uint32 addr);
uint64 ReadLE64(uint32 addr);
void WriteU8(uint32 addr, uint32 data);
void WriteLE16(uint32 addr, uint32 data);
void WriteLE32(uint32 addr, uint32 data);
void WriteLE64(uint32 addr, uint64 data);
uint32 PeekLE16(uint32 addr) const;
uint32 PeekLE32(uint32 addr) const;
// DEVICE_FEATURES の feature 番目のビットを立てる。初期化用。
void SetDeviceFeatures(int feature);
// DEVICE_FEATURES の feature 番目の名前を返す。
virtual const char *GetFeatureName(uint feature) const;
// DRIVER_FEATURES の feature 番目のビットが立っていれば true。
bool GetDriverFeatures(int feature) const;
private:
// 間接ディスクリプタを追加する。
void AddIndirectDesc(VirtIOReq&, uint32 addr, uint32 len);
protected:
uint slot {}; // VirtIO のスロット番号 (0-127)
uint32 device_id {}; // DEVICE_ID
uint32 vendor_id {}; // VENDOR_ID
uint32 device_status {}; // STATUS
uint32 device_sel {}; // DEVICE_FEATURES_SEL
uint32 driver_sel {}; // DRIVER_FEATURES_SEL
std::array<uint32, 2> device_features {}; // DEVICE_FEATURES
std::array<uint32, 2> driver_features {}; // DRIVER_FEATURES
// VirtQueue。継承先で個数が決まっている。
std::vector<VirtQueue> vqueues {};
// QUEUE_SEL の値。
uint32 queue_sel {};
// QUEUE_SEL で選択されている現在の VirtQueue。選択されてなければ NULL。
VirtQueue *vq {};
uint32 intr_status {}; // INTERRUPT_STATUS
// 0x100 以降の領域
std::array<uint8, 0x100> device_config {};
char intrname[10] {}; // 割り込み名
MessageID msgid {}; // 完了通知のメッセージ ID
std::unique_ptr<VirtIOThread> backend /*{}*/;
InterruptDevice *interrupt {};
MainbusDevice *mainbus {};
MainRAMDevice *mainram {};
Monitor *monitor {};
};
// VirtIO 空きスロット用
class VirtIONoneDevice : public IODevice
{
using inherited = IODevice;
public:
VirtIONoneDevice();
~VirtIONoneDevice() override;
protected:
// BusIO インタフェース
static const uint32 NPORT = 0x200 >> 2;
busdata ReadPort(uint32 offset);
busdata WritePort(uint32 offset, uint32 data);
busdata PeekPort(uint32 offset);
};
// 裏スレッド
class VirtIOThread : public ThreadDevice
{
using inherited = ThreadDevice;
protected:
// REQ_QUEUE はキュー1本に1ビットずつ割り当てているので現状最大31まで。
static const uint32 REQ_TERMINATE = 0x80000000; // スレッド終了指示
static constexpr uint32 REQ_QUEUE(uint idx) { // QUEUE_NOTIFY 指示
return (1U << idx);
}
public:
explicit VirtIOThread(VirtIODevice *parent_);
~VirtIOThread() override;
void Terminate() override;
// ゲストからの QUEUE_NOTIFY 要求
void RequestQueueNotify(uint);
private:
void ThreadRun() override;
protected:
// リクエストフラグ
uint32 request {};
std::mutex mtx {};
std::condition_variable cv {};
VirtIODevice *parent {};
};
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.