-- ioctls, filling in as needed
-- note there are some architecture dependent values

local require, error, assert, tonumber, tostring,
setmetatable, pairs, ipairs, unpack, rawget, rawset,
pcall, type, table, string = 
require, error, assert, tonumber, tostring,
setmetatable, pairs, ipairs, unpack, rawget, rawset,
pcall, type, table, string

local function init(types)

local abi = require "syscall.abi"

local s, t = types.s, types.t

local strflag = require "syscall.helpers".strflag

local arch = require("syscall.linux." .. abi.arch .. ".ioctl")

local bit = require "syscall.bit"

local band = bit.band
local function bor(...)
  local r = bit.bor(...)
  if r < 0 then r = r + 4294967296 end -- TODO see note in NetBSD
  return r
end
local lshift = bit.lshift
local rshift = bit.rshift

-- these can vary by architecture
local IOC = arch.IOC or {
  SIZEBITS = 14,
  DIRBITS = 2,
  NONE = 0,
  WRITE = 1,
  READ = 2,
}

IOC.READWRITE = IOC.READ + IOC.WRITE

IOC.NRBITS	= 8
IOC.TYPEBITS	= 8

IOC.NRMASK	= lshift(1, IOC.NRBITS) - 1
IOC.TYPEMASK	= lshift(1, IOC.TYPEBITS) - 1
IOC.SIZEMASK	= lshift(1, IOC.SIZEBITS) - 1
IOC.DIRMASK	= lshift(1, IOC.DIRBITS) - 1

IOC.NRSHIFT   = 0
IOC.TYPESHIFT = IOC.NRSHIFT + IOC.NRBITS
IOC.SIZESHIFT = IOC.TYPESHIFT + IOC.TYPEBITS
IOC.DIRSHIFT  = IOC.SIZESHIFT + IOC.SIZEBITS

local function ioc(dir, ch, nr, size)
  if type(ch) == "string" then ch = ch:byte() end
  return bor(lshift(dir, IOC.DIRSHIFT), 
	     lshift(ch, IOC.TYPESHIFT), 
	     lshift(nr, IOC.NRSHIFT), 
	     lshift(size, IOC.SIZESHIFT))
end

local singletonmap = {
  int = "int1",
  char = "char1",
  uint = "uint1",
  uint32 = "uint32_1",
  uint64 = "uint64_1",
}

local function _IOC(dir, ch, nr, tp)
  if not tp or type(tp) == "number" then return ioc(dir, ch, nr, tp or 0) end
  local size = s[tp]
  local singleton = singletonmap[tp] ~= nil
  tp = singletonmap[tp] or tp
  return {number = ioc(dir, ch, nr, size),
          read = dir == IOC.READ or dir == IOC.READWRITE, write = dir == IOC.WRITE or dir == IOC.READWRITE,
          type = t[tp], singleton = singleton}
end

-- used to create numbers
local _IO    = function(ch, nr)		return _IOC(IOC.NONE, ch, nr, 0) end
local _IOR   = function(ch, nr, tp)	return _IOC(IOC.READ, ch, nr, tp) end
local _IOW   = function(ch, nr, tp)	return _IOC(IOC.WRITE, ch, nr, tp) end
local _IOWR  = function(ch, nr, tp)	return _IOC(IOC.READWRITE, ch, nr, tp) end

-- used to decode ioctl numbers..
local _IOC_DIR  = function(nr) return band(rshift(nr, IOC.DIRSHIFT), IOC.DIRMASK) end
local _IOC_TYPE = function(nr) return band(rshift(nr, IOC.TYPESHIFT), IOC.TYPEMASK) end
local _IOC_NR   = function(nr) return band(rshift(nr, IOC.NRSHIFT), IOC.NRMASK) end
local _IOC_SIZE = function(nr) return band(rshift(nr, IOC.SIZESHIFT), IOC.SIZEMASK) end

-- ...and for the drivers/sound files...

IOC.IN		= lshift(IOC.WRITE, IOC.DIRSHIFT)
IOC.OUT		= lshift(IOC.READ, IOC.DIRSHIFT)
IOC.INOUT		= lshift(bor(IOC.WRITE, IOC.READ), IOC.DIRSHIFT)
local IOCSIZE_MASK	= lshift(IOC.SIZEMASK, IOC.SIZESHIFT)
local IOCSIZE_SHIFT	= IOC.SIZESHIFT

-- VFIO driver writer decided not to use standard IOR/IOW alas
local function vfio(dir, nr, tp)
  local ch = ";"
  nr = nr + 100 -- vfio base
  dir = IOC[string.upper(dir)]
  local io = _IOC(dir, ch, nr, tp)
  if type(io) == "number" then return io end -- if just IO, not return
  io.number = ioc(IOC.NONE, ch, nr, 0) -- number encode nothing, but we want to know anyway
  return io
end

local ioctl = strflag {
-- termios, non standard values generally 0x54 = 'T'
  TCGETS          = {number = 0x5401, read = true, type = "termios"},
  TCSETS          = 0x5402,
  TCSETSW         = 0x5403,
  TCSETSF         = 0x5404,
  TCSBRK          = 0x5409, -- takes literal number
  TCXONC          = 0x540A,
  TCFLSH          = 0x540B, -- takes literal number
  TIOCEXCL        = 0x540C,
  TIOCNXCL        = 0x540D,
  TIOCSCTTY       = 0x540E,
  TIOCGPGRP       = 0x540F,
  TIOCSPGRP       = 0x5410,
  TIOCOUTQ        = 0x5411,
  TIOCSTI         = 0x5412,
  TIOCGWINSZ      = {number = 0x5413, read = true, type = "winsize"},
  TIOCSWINSZ      = {number = 0x5414, write = true, type = "winsize"},
  TIOCMGET        = 0x5415,
  TIOCMBIS        = 0x5416,
  TIOCMBIC        = 0x5417,
  TIOCMSET        = 0x5418,
  TIOCGSOFTCAR    = 0x5419,
  TIOCSSOFTCAR    = 0x541A,
  FIONREAD        = 0x541B,
  TIOCLINUX       = 0x541C,
  TIOCCONS        = 0x541D,
  TIOCGSERIAL     = 0x541E,
  TIOCSSERIAL     = 0x541F,
  TIOCPKT         = 0x5420,
  FIONBIO         = 0x5421,
  TIOCNOTTY       = 0x5422,
  TIOCSETD        = 0x5423,
  TIOCGETD        = 0x5424,
  TCSBRKP         = 0x5425,
  TIOCSBRK        = 0x5427,
  TIOCCBRK        = 0x5428,
  TIOCGSID        = 0x5429,
  TCGETS2         = _IOR('T', 0x2A, "termios2"),
  TCSETS2         = _IOW('T', 0x2B, "termios2"),
  TCSETSW2        = _IOW('T', 0x2C, "termios2"),
  TCSETSF2        = _IOW('T', 0x2D, "termios2"),
  TIOCGPTN        = _IOR('T', 0x30, "uint"),
  TIOCSPTLCK      = _IOW('T', 0x31, "int"),
  TIOCGDEV        = _IOR('T', 0x32, "uint"),
  TCGETX          = 0x5432,
  TCSETX          = 0x5433,
  TCSETXF         = 0x5434,
  TCSETXW         = 0x5435,
  TIOCSIG         = _IOW('T', 0x36, "int"),
  TIOCVHANGUP     = 0x5437,
  FIONCLEX        = 0x5450,
  FIOCLEX         = 0x5451,
  FIOASYNC        = 0x5452,
  TIOCSERCONFIG   = 0x5453,
  TIOCSERGWILD    = 0x5454,
  TIOCSERSWILD    = 0x5455,
  TIOCGLCKTRMIOS  = 0x5456,
  TIOCSLCKTRMIOS  = 0x5457,
  TIOCSERGSTRUCT  = 0x5458,
  TIOCSERGETLSR   = 0x5459,
  TIOCSERGETMULTI = 0x545A,
  TIOCSERSETMULTI = 0x545B,
  TIOCMIWAIT      = 0x545C,
  TIOCGICOUNT     = 0x545D,
  FIOQSIZE        = 0x5460,
-- socket ioctls from linux/sockios.h - for many of these you can use netlink instead
  FIOSETOWN       = 0x8901,
  SIOCSPGRP       = 0x8902,
  FIOGETOWN       = 0x8903,
  SIOCGPGRP       = 0x8904,
  SIOCATMARK      = 0x8905,
  SIOCGSTAMP      = 0x8906,
  SIOCGSTAMPNS    = 0x8907,

  SIOCADDRT       = 0x890B,
  SIOCDELRT       = 0x890C,
  SIOCRTMSG       = 0x890D,

  SIOCGIFFLAGS    = 0x8913,
  SIOCSIFFLAGS    = 0x8914,
  SIOCGIFMTU      = 0x8921,
  SIOCSIFMTU      = 0x8922,
  SIOCSIFHWADDR   = 0x8924,
  SIOCGIFHWADDR   = 0x8927,
  SIOCGIFINDEX    = 0x8933,

  SIOCDARP        = 0x8953,
  SIOCGARP        = 0x8954,
  SIOCSARP        = 0x8955,

  SIOCBRADDBR     = 0x89a0,
  SIOCBRDELBR     = 0x89a1,
  SIOCBRADDIF     = 0x89a2,
  SIOCBRDELIF     = 0x89a3,
-- event system
  EVIOCGVERSION   = _IOR('E', 0x01, "int"),
  EVIOCGID        = _IOR('E', 0x02, "input_id"),
  EVIOCGREP       = _IOR('E', 0x03, "uint2"),
  EVIOCSREP       = _IOW('E', 0x03, "uint2"),
  EVIOCGKEYCODE   = _IOR('E', 0x04, "uint2"),
  EVIOCGKEYCODE_V2 = _IOR('E', 0x04, "input_keymap_entry"),
  EVIOCSKEYCODE   = _IOW('E', 0x04, "uint2"),
  EVIOCSKEYCODE_V2 = _IOW('E', 0x04, "input_keymap_entry"),
  EVIOCGNAME = function(len) return _IOC(IOC.READ, 'E', 0x06, len) end,
  EVIOCGPHYS = function(len) return _IOC(IOC.READ, 'E', 0x07, len) end,
  EVIOCGUNIQ = function(len) return _IOC(IOC.READ, 'E', 0x08, len) end,
  EVIOCGPROP = function(len) return _IOC(IOC.READ, 'E', 0x09, len) end,
  EVIOCGKEY  = function(len) return _IOC(IOC.READ, 'E', 0x18, len) end,
  EVIOCGLED  = function(len) return _IOC(IOC.READ, 'E', 0x19, len) end,
  EVIOCGSND  = function(len) return _IOC(IOC.READ, 'E', 0x1a, len) end,
  EVIOCGSW   = function(len) return _IOC(IOC.READ, 'E', 0x1b, len) end,
  EVIOCGBIT  = function(ev, len) return _IOC(IOC.READ, 'E', 0x20 + ev, len) end,
  EVIOCGABS  = function(abs) return _IOR('E', 0x40 + abs, "input_absinfo") end,
  EVIOCSABS  = function(abs) return _IOW('E', 0xc0 + abs, "input_absinfo") end,
  EVIOCSFF   = _IOC(IOC.WRITE, 'E', 0x80, "ff_effect"),
  EVIOCRMFF  = _IOW('E', 0x81, "int"),
  EVIOCGEFFECTS = _IOR('E', 0x84, "int"),
  EVIOCGRAB  = _IOW('E', 0x90, "int"),
-- input devices
  UI_DEV_CREATE  = _IO ('U', 1),
  UI_DEV_DESTROY = _IO ('U', 2),
  UI_SET_EVBIT   = _IOW('U', 100, "int"),
  UI_SET_KEYBIT  = _IOW('U', 101, "int"),
-- tun/tap
  TUNSETNOCSUM   = _IOW('T', 200, "int"),
  TUNSETDEBUG    = _IOW('T', 201, "int"),
  TUNSETIFF      = _IOW('T', 202, "int"),
  TUNSETPERSIST  = _IOW('T', 203, "int"),
  TUNSETOWNER    = _IOW('T', 204, "int"),
  TUNSETLINK     = _IOW('T', 205, "int"),
  TUNSETGROUP    = _IOW('T', 206, "int"),
  TUNGETFEATURES = _IOR('T', 207, "uint"),
  TUNSETOFFLOAD  = _IOW('T', 208, "uint"),
  TUNSETTXFILTER = _IOW('T', 209, "uint"),
  TUNGETIFF      = _IOR('T', 210, "uint"),
  TUNGETSNDBUF   = _IOR('T', 211, "int"),
  TUNSETSNDBUF   = _IOW('T', 212, "int"),
  TUNATTACHFILTER= _IOW('T', 213, "sock_fprog"),
  TUNDETACHFILTER= _IOW('T', 214, "sock_fprog"),
  TUNGETVNETHDRSZ= _IOR('T', 215, "int"),
  TUNSETVNETHDRSZ= _IOW('T', 216, "int"),
  TUNSETQUEUE    = _IOW('T', 217, "int"),
-- from linux/vhost.h VHOST_VIRTIO 0xAF
  VHOST_GET_FEATURES   = _IOR(0xAF, 0x00, "uint64"),
  VHOST_SET_FEATURES   = _IOW(0xAF, 0x00, "uint64"),
  VHOST_SET_OWNER      = _IO(0xAF, 0x01),
  VHOST_RESET_OWNER    = _IO(0xAF, 0x02),
  VHOST_SET_MEM_TABLE  = _IOW(0xAF, 0x03, "vhost_memory"),
  VHOST_SET_LOG_BASE   = _IOW(0xAF, 0x04, "uint64"),
  VHOST_SET_LOG_FD     = _IOW(0xAF, 0x07, "int"),
  VHOST_SET_VRING_NUM  = _IOW(0xAF, 0x10, "vhost_vring_state"),
  VHOST_SET_VRING_ADDR = _IOW(0xAF, 0x11, "vhost_vring_addr"),
  VHOST_SET_VRING_BASE = _IOW(0xAF, 0x12, "vhost_vring_state"),
  VHOST_GET_VRING_BASE = _IOWR(0xAF, 0x12, "vhost_vring_state"),
  VHOST_SET_VRING_KICK = _IOW(0xAF, 0x20, "vhost_vring_file"),
  VHOST_SET_VRING_CALL = _IOW(0xAF, 0x21, "vhost_vring_file"),
  VHOST_SET_VRING_ERR  = _IOW(0xAF, 0x22, "vhost_vring_file"),
  VHOST_NET_SET_BACKEND= _IOW(0xAF, 0x30, "vhost_vring_file"),
-- from linux/vfio.h type is ';' base is 100
  VFIO_GET_API_VERSION = vfio('NONE', 0),
  VFIO_CHECK_EXTENSION = vfio('WRITE', 1, "uint32"),
-- from linux/perf_event.h
  PERF_EVENT_IOC_ENABLE    = _IO('$', 0),
  PERF_EVENT_IOC_DISABLE   = _IO('$', 1),
  PERF_EVENT_IOC_REFRESH   = _IO('$', 2),
  PERF_EVENT_IOC_RESET     = _IO('$', 3),
  PERF_EVENT_IOC_PERIOD    = _IOW('$', 4, "uint64"),
  PERF_EVENT_IOC_SET_OUTPUT= _IO('$', 5),
  PERF_EVENT_IOC_SET_FILTER= _IOW('$', 6, "uintptr"),
  PERF_EVENT_IOC_ID        = _IOR('$', 7, "uint64_1"),
  PERF_EVENT_IOC_SET_BPF   = _IOW('$', 8, "uint32"),

-- allow user defined ioctls
  _IO = _IO,
  _IOR = _IOR, 
  _IOW = _IOW,
  _IOWR = _IOWR,
}

local override = arch.ioctl or {}
if type(override) == "function" then override = override(_IO, _IOR, _IOW, _IOWR) end
for k, v in pairs(override) do ioctl[k] = v end

-- allow names for types in table ioctls
for k, v in pairs(ioctl) do if type(v) == "table" and type(v.type) == "string" then v.type = t[v.type] end end

-- alternate names
ioctl.TIOCINQ = ioctl.FIONREAD

return ioctl

end

return {init = init}

