在modules.list中帶uvmm的entry會在moe中啟動uvmm.tmgr。
entry uvmm-zcu102 kernel fiasco-serial_esc roottask moe rom/uvmm.tmgr module uvmm module l4re module tmgr module hello module cons module io module virt-zcu102.dtb module[shell] echo $SRC_BASE_ABS/pkg/uvmm/configs/vmm.lua module[shell] echo $SRC_BASE_ABS/pkg/uvmm/configs/bsp/plat-zcu102/io.cfg module[shell] echo $SRC_BASE_ABS/pkg/uvmm/configs/bsp/plat-zcu102/vm_pass.vbus module[shell] echo $SRC_BASE_ABS/pkg/uvmm/configs/bsp/plat-zcu102/uvmm.tmgr module Image_zc102_ubuntu |
而這個uvmm.tmgr就是io程序(@1)和uvmm程序(@2)的入口。如下:
…… -- virtual IO busses local io_busses = { vm_pass = 1, } …… vmm.start_io(io_busses, "rom/io.cfg"); //@1 vmm.start_vm{ //@2 id=1, mem=1024, kernel="rom/bzImage", vbus=io_busses.vm_pass, fdt="rom/virt-pc.dtb", …… }; |
vmm.start_io和vmm.start_vm定義在vmm.lua中
function start_io(busses, opts) …… for k, v in pairs(busses) do local c = l:new_channel(); busses[k] = c //建立一個ipc通道指派給vm_pass caps[k] = c:svr(); files = files .. " rom/" .. k .. ".vbus"; //files指派為“rom/vm_pass.vbus” end return l:start({ log = { "io", "red" }, caps = caps }, "rom/io " .. opts .. files) //啟動程序“rom/io”,并将opts("rom/io.cfg")和files(“rom/vm_pass.vbus”)作為其參數 End |
function start_vm(options) …… local vbus = options.vbus; //本地變量vbus賦……值為“vm_pass” local caps = { net = vnet; vbus = vbus; //本地變量caps(其中包含vbus) …… }; …… local opts = { --log = l.log_fab:create(ZRE.Proto.Log, "vm" .. nr, "w", keyb_shortcut); log = ZRE.Env.log:m("rws"); caps = caps; //本地變量opts(包含caps) }; set_sched(opts, prio, cpus); return l:startv(opts, "rom/uvmm", table.unpack(cmdline)); //将opts作為能力權限,啟動 "rom/uvmm"程序,并将處理過後的cmdline作為參數傳給uvmm end |
綜上,在io程序中會對“io.cfg”和“vm_pass.vbus”進行解析;在uvmm程序中會通過擷取vm_pass的能力權限,進行vbus資源申請。
下面分别從io程序中vbus總線的建構和uvmm中對vbus總線資源的申請兩方面進行vbus機制的分析。
io程序中vbus總線的建構
Io程序在進行了lua運作環境初始化及其他一些初始化工作後,就會依次讀入傳入的兩個參數(lua檔案——io.cfg和vm_pass.vbus),并進行解析。
io.cfg檔案解析
io.cfg檔案結構如下:
Io.Dt.add_children(Io.system_bus(), function() VGIC = Io.Hw.Device(function() Property.hid = "arm-gicc" Resource.reg0 = Io.Res.mmio(0xf9060000, 0xf907ffff); Property.flags = Io.Hw_device_DF_multi_vbus; end) …… end) |
Lua腳本中Io.Dt.add_children定義如下:
function Io.Dt.add_children(parent, bus_func) check_device(parent, 2); if type(bus_func) == "function" then local d = { Property = {}, Resource = {}, Child = {} } …… debug.upvaluejoin(bus_func, env_upval, function() return d end, 1) // 後面詳解 bus_func(); //此處會逐個執行上表中“Io.Hw.Device(function()”程式段 Io.Dt.add_device_data(parent, d) //添加到父節點 end return parent; end |
debug.upvaluejoin(bus_func, env_upval, function() return d end, 1):函數解釋如下
debug.upvaluejoin (f1, n1, f2, n2) 讓 Lua 閉包 f1 的第 n1 個上值 引用 Lua 閉包 f2 的第 n2 個上值。 |
這裡bus_func的env_upval指的是“_ENV”,而後面那個function的第一個upvalue指的是前面定義的local d;而lua中變量的定義如a=1,會被編譯為_ENV.a=1,這裡講“_ENV”引用d,是以編譯會變為d.a=1;是以bus_func中所有的變量定義都會放入d這個table裡。
Io.Hw定義如下表,當使用“Io.Hw.Device”調用時,由于Io.Hw元表的__index設定為function屬性,是以Device會作為t的實參傳入;更進一步使用“Io.Hw.Device(function()”調用時,Device的參數function會作為其内部閉包“function (data)”的data參數傳入。
Io.Hw = {} setmetatable(Io.Hw, { __index = function (self, t) return function (data) local b = check_device(Io.Hw_dev_factory_create(t), 3, "could not create device: " .. t) //建立裝置 if type(data) == "function" then add_children(b, data) //添加子節點 end return b //傳回帶資源資訊的Device節點 end end}) |
建立裝置
函數Hw_dev_factory_create的定義可以從由SWIG工具生成的代碼檔案——lua_glue.swg.cc中找到:
static swig_lua_method swig_SwigModule_methods[]= { { "swig_class",swig_class}, { "swig_instance_of",swig_instance_of}, { "Resource_str_to_id", _wrap_Resource_str_to_id}, { "Vi_dev_factory_create", _wrap_Vi_dev_factory_create}, { "Hw_dev_factory_create", _wrap_Hw_dev_factory_create}, { "system_bus", _wrap_system_bus}, { "dump_devs", _wrap_dump_devs}, { "add_vbus", _wrap_add_vbus}, {0,0} }; |
是以調用關系為Hw_dev_factory_create——》_wrap_Hw_dev_factory_create——》_wrap_Hw_dev_factory_create__SWIG_1——》Hw::Device_factory::create,建立好裝置(Hw::Device *)指派給本地變量b。
Device * Device_factory::create(cxx::String const &name) { Name_map::const_iterator i = nm().find(std::string(name.start(), name.end())); if (i != nm().end()) return i->second->create(); //調用注冊的子類建立 return 0; } |
在hw_device.cc中靜态定義了Device的工廠類
static Device_factory_t<Device> __hw_pf_factory("Device") |
添加子節點
local add_children = Io.Dt.add_children |
是以,又遞歸調用Io.Dt.add_children函數。本次會将裝置的Property和Resource 填充到本地變量d。然後,調用Io.Dt.add_device_data函數将d添加到上一步建立的裝置的子節點中。
function Io.Dt.add_device_data(dev, data) local maxi = 0 for i, v in ipairs(data) do handle_device_member(dev, v, i) maxi = i end for k, v in pairs(data) do if (type(k) ~= "number") or (k > maxi) then handle_device_member(dev, v, k) end end end |
local function handle_device_member(dev, val, name) local vtype = type(val) if name == "compatible" then …… dev:add_cid(val) //compatible屬性,調用“Hw::Device::add_cid”加入裝置cid清單 …… elseif name == "Property" then //Property屬性添加,調用Generic_device::property …… elseif name == "Resource" then //Resource屬性添加 …… v:set_id(k) //設定ResourceID(regX、irqX) dev:add_resource(v) //調用Generic_device::add_resource …… elseif name == "Child" then //如果有Child屬性,則處理 …… elseif Io.swig_instance_of(val, "Resource *") then …… elseif Io.swig_instance_of(val, "Generic_device *") then add_child(dev, name, val) //如果是裝置類型,則加入父節點 return elseif vtype == "table" then …… end |
傳回帶資源資訊的Device節點
這一步将建立好的,帶資源資訊的Device節點傳回,如io.cfg檔案中:
VGIC = Io.Hw.Device(function() …… end) |
Io.Hw.Device調用後會将Device節點資訊指派給VGIC 對象。
這裡有幾點需要說明下:
Io.system_bus()——》_wrap_system_bus——》(Hw::Device *)system_bus()——》Hw::Root_bus *hw_system_bus()——》Hw::Root_bus _sb("System Bus")
Io.Res.mmio——》_proxy__wrap_new_Resource——》_wrap_new_Resource——》_wrap_new_Resource__SWIG_0——》(Resource *)new Resource(Resource::Mmio_res)
Io.Res.irq,同上,最後建立資源傳入“Resource::Irq_res”參數
Io.Hw_device_DF_multi_vbus:設定裝置可供多vbus總線共用,目前隻是在沒設此flag又多總線引用情況下輸出一條warning,沒做進一步處理。
Io.cfg解析的最後一步是将建立的所有Device加入到root device中:
Io.Dt.add_device_data(parent, d) |
此時的d是個什麼狀态呢?如下:
local d = { Property = {}, Resource = {}, Child = {}, VGIC = ……, VSPI = ……, …… } |
是以會程式會走到handle_device_member函數特殊處理部分的val為"Generic_device *"場景,然後調用add_child(即Io.Dt.add_child),将各子節點加入vbus根節點:
function Io.Dt.add_child(parent, name, dev, idx) parent:add_child(dev) //調用Hw::Device::add_child if dev.plugin and (parent:parent() or swig_equals(parent, Io.system_bus())) then dev:plugin() //調用裝置init函數初始化 end if type(name) == "string" then if idx ~= nil then name = name .. '[' .. idx ..']' end dev:set_name(name) //設定裝置名字,如VGIC、VSPI end end |
Io.cfg的解析完成。
vm_pass.vbus檔案解析
檔案結構如下:
Io.add_vbusses { vm_pass = Io.Vi.System_bus(function() VGIC = wrap(Io.system_bus():match("arm-gicc")) …… end); } |
Io.add_vbusses
函數實作如下:
function Io.add_vbusses(busses) for name, bus in pairs(busses) do Io.add_vbus(name, bus) end return busses end |
function Io.add_vbus(name, bus) bus:set_name(name) add_vbus(bus) end |
上表中name就是“vm_pass”,bus就是“Io.Vi.System_bus(function()……”的傳回值。同上面io.cfg解析一樣,Io.Vi設定了元表:
Io.Vi = {} setmetatable(Io.Vi, { __index = function (self, t) //System_bus作為t傳入,其後“function()……”作為data傳入 return function (data) local b = Io.Vi_dev_factory_create(t) //建立虛拟裝置(System_bus) if type(data) == "function" then add_children(b, data) //添加子節點 elseif type(data) == "table" then set_dev_data(b, data) end return b end end}) |
建立虛拟裝置(System_bus)
調用關系為Io.Vi_dev_factory_create(t)——》_wrap_Vi_dev_factory_create——》_wrap_Vi_dev_factory_create__SWIG_0——》Vi::Dev_factory::create;create函數如下:
namespace Vi { Device * Dev_factory::create(std::string const &_class) //_class值為“System_bus” { Name_map &m = name_map(); Name_map::iterator i = m.find(_class); //在靜态變量static Name_map _name_map中查找 if (i == m.end()) { d_printf(DBG_WARN, "WARNING: cannot create virtual device: '%s'\n", _class.c_str()); return 0; } return i->second->vcreate(); //找到則調用其vcreate函數建立裝置 } } |
由于類Dev_factory_t<V_DEV, void>繼承自Dev_factory,且其在構造函數中将自己添加到_name_map中,是以系統中定義的Dev_factory_t<V_DEV, void>類型的工廠類都會添加到_name_map中,如下:
template< typename V_DEV > class Dev_factory_t<V_DEV, void> : public Dev_factory { …… explicit Dev_factory_t(std::string const &_class) : Dev_factory(0) { name_map()[_class] = this; } …… Device *vcreate() { return new V_dev; } }; |
在目前版本的zeos中靜态變量 _name_map中定義的工廠有:
static Vi::Dev_factory_t<Virtual_sbus> __sb_root_factory("System_bus"); static Dev_factory_t<Gpio, Hw::Gpio_device> __gpio_factory; static Dev_factory_t<Pci_dummy> __pci_dummy_factory("PCI_dummy_device"); static Dev_factory_t<Pci_to_pci_bridge> __pci_to_pci_factory("PCI_PCI_bridge"); static Dev_factory_t<Pci_vroot> __pci_root_factory("PCI_bus"); static Dev_factory_t<Proxy_dev, Hw::Device> __ghwdf; |
是以,會建立一個名字為“System_bus”的Virtual_sbus類型的根裝置__sb_root_factory。
添加子節點
調用Io.Dt.add_children進行子節點添加,Io.Dt.add_children函數分析見io.cfg解析部分。
其中“bus_func”子節點的建立如下:
VGIC = wrap(Io.system_bus():match("arm-gicc")) |
function wrap(devs_, filter) local devs = devs_ if type(devs_) ~= "table" then devs = { devs_ } end local v = {} for _, d in ipairs(devs) do local vd = Io.Vi_dev_factory_create(d) //建立裝置 if vd then if type(filter) == "table" then for tag, val in pairs(filter) do local res = vd:add_filter_val(tag, val) if res < 0 then print("ERROR: applying filter expression: "..tag.."=...", debug.traceback(2)) end end end v[#v + 1] = vd //将建立的裝置加入table——v end end if #v == 1 then //傳回裝置或裝置table return v[1] else return v end end |
wrap函數的參數,調用Io.system_bus()的match方法,在lua腳本裡:
match = Io.Dt.match, |
function Io.Dt.match(self, ...) //self就是指調用者——Io.system_bus() local cids = {...} //變參清單,此處(例如第一項)指"arm-gicc" for t,v in pairs(Io.Dt.PCI_cc) do //字元串處理,arm架構字元串沒有改變 for i, cid in ipairs(cids) do cids[i] = cid:gsub("(PCI/"..t..")", "PCI/" .. v) //lua庫函數——string.gsub應用;将cid中格式為(PCI/storage)(storage為舉例)的字元串替換為PCI/CC_01(CC_01為與storage對應的舉例) end end local devs = {} for d in self:devices(Io.Dt.MAX_DEPTH) do //周遊裝置 if d:match_cids(table.unpack(cids)) then //調用match_cids進行比對 devs[#devs+1] = d end end return devs //将所有比對的裝置傳回 end |
周遊裝置
self:devices方法在lua腳本中定義為Io.Dt.iterator
function Io.Dt.iterator(dev, max_depth) //dev為調用者self傳入即——Io.system_bus() …… //函數體略過。周遊傳回子節點 end |
調用match_cids進行比對
d:match_cids方法在lua腳本中定義為Io.Dt.match_cids
function Io.Dt.match_cids(self, ...) local r = {} for _, v in ipairs{...} do if self:match_cid(v) then //調用裝置的match_cid函數 return true end end return false end |
self:match_cid——》_wrap_Hw_device_match_cid——》Hw::Device::match_cid
将所有比對的裝置傳回
這裡有點需要注意,就是傳回的是所有比對的裝置,即一個compatible字元串可以比對多個裝置。
上面就是所有裝置節點的建立,最後調用Io.add_vbus
function Io.add_vbus(name, bus) bus:set_name(name) //調用Vi::Device::set_name設定名字為vm_pass add_vbus(bus) //調用add_vbus函數(main.cc中定義) end |
int add_vbus(Vi::Device *dev) { Vi::System_bus *b = dynamic_cast<Vi::System_bus*>(dev); if (!b) { d_printf(DBG_ERR, "ERROR: found non system-bus device as root device, ignored\n"); return -1; } b->request_child_resources(); b->allocate_pending_child_resources(); b->setup_resources(); if (!registry->register_obj(b, b->name()).is_valid()) //注冊服務 { d_printf(DBG_WARN, "WARNING: Service registration failed: '%s'\n", b->name()); return -1; } if (dlevel(DBG_DEBUG2)) dump(b); return 0; } |
vm_pass.vbus解析完成。
uvmm中對vbus總線資源的申請
在uvmm中通過main()——》run()——》create_default_devices()進行vbus總線資源申請:
void create_default_devices(zre_addr_t rambase) { …… auto vbus_cap = e->get_cap<ZREvbus::Vbus>("vbus"); //擷取vbus能力權限 if (!vbus_cap) vbus_cap = e->get_cap<ZREvbus::Vbus>("vm_bus"); _vbus = cxx::make_ref_obj<Vmm::Virt_bus>(vbus_cap); //建立vbus對象 …… } |
擷取vbus能力權限
vbus能力權限在前文講vmm.lua的start_vm函數時,傳遞到虛拟機uvmm。其最終關聯的是Io Server的vm_pass虛拟總線。
建立vbus對象
cxx::make_ref_obj<Vmm::Virt_bus>(vbus_cap)會調用Virt_bus的構造函數:
explicit Virt_bus(ZRE::Cap<ZREvbus::Vbus> bus) : _bus(bus) { …… scan_bus(); } |
void Virt_bus::scan_bus() { ZREvbus::Device root = _bus->root(); //建立根裝置 Devinfo info; while (root.next_device(&info.io_dev, ZREVBUS_MAX_DEPTH, &info.dev_info) == 0) _devices.push_back(info); //擷取vbus所有子節點,并儲存在_devices中 } |
int next_device(Device *child, int depth = ZREVBUS_MAX_DEPTH, zrevbus_device_t *devinfo = 0) const { child->_bus = _bus; //_dev初始化時指派為ZREVBUS_ROOT_BUS=0 return zrevbus_get_next_device(_bus.cap(), _dev, &child->_dev, depth, devinfo); } |
int zrevbus_get_next_device(zre_cap_idx_t vbus, zrevbus_device_handle_t parent, zrevbus_device_handle_t *child, int depth, zrevbus_device_t *devinfo) { ZRE::Ipc::Iostream s(zre_utcb()); s << parent << zre_uint32_t(ZREvbus_vdevice_get_next) << *child << depth; int err = zre_error(s.call(vbus, ZREvbus::Vbus::Protocol)); if (err < 0) return err; s >> *child; if (devinfo) s.get(*devinfo); return err; } |
可以看出,擷取vbus子節點過程是通過IPC,擷取後全部儲存在本地變量_devices中。
在uvmm中根據dtb建立裝置流程中,查找比對裝置會調用Virt_bus::find_unassigned_dev方法,還會涉及與Io Server的IPC互動過程。