天天看點

vbus機制之lua代碼注釋

在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互動過程。