Lunatik

deviceModule

Low-level Lua interface for creating Linux character device drivers.

This module allows Lua scripts to implement character device drivers by providing callback functions for standard file operations like open, read, write, and release.

A file operation runs on the task performing it, in process context, so its callback may sleep. It runs under the lock of the runtime that made the device, so one the runtime’s own code performs, a callback or a thread body opening the node it made, fails with EDEADLK: it would wait on the lock its task holds. An open, read or write callback fails its operation with an errno by returning it negated, as -errno.EBUSY does with linux.errno. A callback that raises, or that returns a length or an offset that is not an integer, or an errno that is not one, the kernel’s own from ERESTARTSYS on included, invalid errno in the log, fails its operation with ECANCELED, and the error goes to the kernel log.

Class device

device:stop () Stops a character device driver and removes it from the system.

device

new (driver) Creates and installs a new character device driver in the system.

Class device in detail

Represents a character device implemented in Lua. This is a userdata object returned by device.new(). It encapsulates the necessary kernel structures (struct cdev, dev_t) to manage a character device, linking file operations to Lua callback functions.
device:stop ()
Stops a character device driver and removes it from the system. This method is called on a device object returned by device.new(): the device file (/dev/<name>) and its region go with it. Without stop, the device lives until its runtime stops, since device.new keeps the object for the runtime. A file still open on the device gets ENXIO from read and write until it is closed. A to-be-closed variable holding the device stops it the same way.

Returns

    nil Does not return any value to Lua.

Raises

if the argument is not a device.

See also

Usage

-- Assuming 'dev' is a device object:
dev:stop()

device in detail

new (driver)
Creates and installs a new character device driver in the system. This function binds a Lua table (the driver table) to a new character device file (/dev/<name>), allowing Lua functions to handle file operations on that device.

Parameters

  • driver table

    A table defining the device driver’s properties and callbacks. It must contain the field:

    • name (string): The name of the device. This name will be used to create the device file /dev/<name>.

    It might optionally contain the following fields (callback functions), each of which receives, after its own arguments, file: a table private to one open of the device, the same in every callback that open reaches from its open to its release, where the driver keeps what belongs to that open.

    • open (function): Callback for the open(2) system call. Signature: function(driver_table, file) -> [errno]. Returning nothing or zero opens the file, and returning a negative errno, -errno.EBUSY say, fails the open with it.
    • read (function): Callback for the read(2) system call. Signature: function(driver_table, length, offset, file) -> string [, updated_offset]. Receives the driver table, the requested read length (integer), and the current file offset (integer). Should return the data as a string and optionally the updated file offset (integer), or a negative errno, -errno.EAGAIN say, which fails the read with it. If updated_offset is not returned, the offset is advanced by the length of the returned string (or the requested length if the string is longer).
    • write (function): Callback for the write(2) system call. Signature: function(driver_table, buffer_string, offset, file) -> [written_length] [, updated_offset]. Receives the driver table, the data to write as a string, and the current file offset (integer). May return the number of bytes successfully written (integer), or a negative errno, -errno.ENOSPC say, which fails the write with it, and optionally the updated file offset (integer). If written_length is not returned, it’s assumed all provided data was written. If updated_offset is not returned, the offset is advanced by the written_length.
    • release (function): runs when the last reference to an open file is closed, the final close(2). Signature: function(driver_table, file). Expected to return nothing.

    A read returns end of file when its callback returns an empty string, nil or zero.

    It might also contain the field:

    • mode (integer): Optional permission bits of the device file, within S_IALLUGO: the file type is the kernel’s, a character device. Use constants from linux.stat (e.g., stat.IRUGO).

Returns

    device the device, which device.new keeps for its runtime: dropping it stops nothing, and the device stays until stop or the end of the runtime.

Raises

Error if the device cannot be allocated or registered in the kernel, if the name field is missing or not a string, or if called from a percpu runtime; bad field 'mode' (number expected, got <type>) if mode is present and not a number; bad field '<field>' (function expected, got <type>) if open, read, write or release is present and not a function; bad field 'mode' (out of bounds) if it is negative or past S_IALLUGO, as a mode that carries a file type is; 'device': process-context class in interrupt-context runtime in a softirq or hardirq runtime (run it in process context, the default of lunatik run); not allowed while the runtime closes from a finalizer that runs at its close.

Usage

local device = require("device")
local stat   = require("linux.stat")

local function read(drv, len, off)
  local data = "Hello from " .. drv.name .. "!\n"
  return data:sub(off + 1, off + len)
end

local my_driver = {
  name = "my_lua_device",
  mode = stat.IRUGO, -- Read-only for all
  read = read,
}
local dev_obj = device.new(my_driver)
-- To remove it: dev_obj:stop(), or stop the runtime.