在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交互过程。