Skip to content

Commit

Permalink
pci.lua, intel10g.lua: Load a dedicated Lua module for each NIC port.
Browse files Browse the repository at this point in the history
This is an experiment in Lua coding style. Now device driver modules
can be written as if there is only one device to control. The PCI
address of this device is known when the module is loaded.

This is my current favorite of the three main programming styles that
I have seen for using the same driver module for multiple devices:

1. Table-based OO. The discipline of Using `self:foo()` and `self.foo`
   to access everything that is unique for each device.
2. Closure-based OO. Wrapping the whole driver in a `new()` function
   and defining `local` variables for everything that is unique for each device.
3. Module-based OO. The module code is specific to one device and no
   special programming discipline is needed. The module needs to be
   loaded in a special way and reloaded for each instance. (This is
   what this commit implements.)

Feedback welcome!
  • Loading branch information
lukego committed Apr 6, 2013
1 parent 949fee9 commit b44c0fb
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
18 changes: 14 additions & 4 deletions src/intel10g.lua
@@ -1,6 +1,17 @@
-- intel10g.lua -- Intel 82599 10GbE ethernet device driver

module(...,package.seeall)
--- This module defines a device driver for one specific PCI device.
--- The load-time `...` argument must be bound to two values:
--- * The unique name for this module instance.
--- * The PCI address for the device this instance manages.

local moduleinstance,pciaddress = ...

if pciaddress == nil then
print("WARNING: intel10g loaded as normal module; should be device-specific.")
end

module(moduleinstance,package.seeall)

local ffi = require "ffi"
local C = ffi.C
Expand All @@ -25,9 +36,8 @@ r = {}
s = {}

function open ()
local pcidev = "0000:83:00.1"
pci.set_bus_master(pcidev, true)
base = ffi.cast("uint32_t*", pci.map_pci_memory(pcidev, 0))
pci.set_bus_master(pciaddress, true)
base = ffi.cast("uint32_t*", pci.map_pci_memory(pciaddress, 0))
register.define(config_registers_desc, r, base)
register.define(statistics_registers_desc, s, base)
init_device()
Expand Down
48 changes: 48 additions & 0 deletions src/pci.lua
Expand Up @@ -109,11 +109,46 @@ function is_usable (info)
return info.driver and (info.interface == nil or info.status == 'down')
end

--- ## Open a device
---
--- Load a fresh copy of the device driver's Lua module for each
--- device. The module will be told at load-time the PCI address of
--- the device it is controlling. This makes the module code short
--- because it can assume that it's always talking to the same device.
---
--- This is achieved with our own require()-like function that loads a
--- fresh copy and passes the PCI address as an argument.

open_devices = {}

-- Load a new instance of the 'driver' module for 'pciaddress'.
-- On success this creates the Lua module 'driver@pciaddress'.
--
-- Example: open_device("intel10g", "0000:83:00.1") creates module
-- "intel10g@0000:83:00.1" which controls that specific device.
function open_device(pciaddress, driver)
local instance = driver.."@"..pciaddress
find_loader(driver)(instance, pciaddress)
open_devices[pciaddress] = package.loaded[instance]
return package.loaded[instance]
end

-- (This could be a Lua builtin.)
-- Return loader function for `module`.
-- Calling the loader function will run the module's code.
function find_loader (mod)
for i = 1, #package.loaders do
status, loader = pcall(package.loaders[i], mod)
if type(loader) == 'function' then return loader end
end
end

--- ## Selftest

function selftest ()
print("selftest: pci")
print_devices()
open_usable_devices()
end

--- Print a table summarizing all the available hardware devices.
Expand All @@ -131,6 +166,19 @@ function print_devices ()
end
end

function open_usable_devices ()
for _,device in ipairs(devices) do
if device.usable == 'yes' then
print("Unbinding device from linux: "..device.pciaddress)
unbind_device_from_linux(device.pciaddress)
print("Opening device "..device.pciaddress)
local driver = open_device(device.pciaddress, device.driver)
print("Testing "..device.pciaddress)
driver.selftest()
end
end
end

function module_init () scan_devices () end

module_init()
Expand Down

1 comment on commit b44c0fb

@pkazmier
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How different is invoking driver.selftest() vs driver:selftest()? From a calling perspective, the difference is the choice of a character. I assume your real point of contention is with the use of self in the definition of the module. I understand you feel it is cumbersome, but I guess after years of Python, I've become accustomed to self (in Python it's even worse as you have to explicitly define that first argument in every function definition!). Aside from self, I've been using the regular OO-idiom, but I like the idea of parameterizing the require to instantiate an object. Using this method, you could create instances in this manner:

local a = require 'intel10g' { pciaddress = '0000:83:00.1' }
local b = require 'intel10g' { pciaddress = '0000:83:00.2' }
local c = require 'intel10g' { pciaddress = '0000:83:00.3' }

a:selftest()
b:selftest()
c:selftest()

The module would then be defined like:

local M = {}

function M.init(o)
  return M:new(o)
end

function M:new(o)
  o = o or {}
  setmetatable(o, M)
  M.__index = M
  return o
end

function M:selftest()
  print(self.pciaddress)
end

return M.init

Interesting discussion! Ultimately, it makes no difference to me. I suppose the only benefit to the standard approach is to make it easier for newcomers to Snabb Switch to contribute. If they are existing Lua users, the common idiom may be easier to understand, and if they are not, newbies like myself will simply see the common idiom peppered throughout all of the Lua documentation/books. Having said that though, Lua is sufficiently vague and it encourages one to experiment so it would almost be a disservice if you purposely didn't mix things up!

Please sign in to comment.