From: Luke Kenneth Casson Leighton Date: Fri, 27 Apr 2018 03:53:24 +0000 (+0100) Subject: move ioctl-like to separate page X-Git-Tag: convert-csv-opcode-to-binary~5449 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=ffc571413d31e5157b1924aac9114c72fae9ad97;p=libreriscv.git move ioctl-like to separate page --- diff --git a/isa_conflict_resolution.mdwn b/isa_conflict_resolution.mdwn index d96c5eeaf..3223e2d26 100644 --- a/isa_conflict_resolution.mdwn +++ b/isa_conflict_resolution.mdwn @@ -338,24 +338,7 @@ correct implementation of (mandatory) support for MISA. (Summary: good solid orthogonal idea. See [[ioctl]] for full details) -==RB=== - -This proposal adds a standardised extension interface to the RV instruction set by introducing a fixed small number (e.g. 8) of "overloadable" R-type opcodes ext_ctl0, .. ext_ctl7. Each takes a process local interface cookie in rs1. Based on the cookie, the CPU routes the "overloaded" instructions to a "device" on or off the CPU that implements the actual semantics. - -The cookie is "opened" with an additional r-type instruction ext_open that takes a 20 bit identifier and "closed" with an ext_close instruction. The implementing hardware device can use the cookie to reference internal state. Thus, interfaces may be statefull. - -CPU's and devices may implement several interfaces, indeed, are expected to. E.g. a single hardware device might expose a functional interface with 6 overloaded instructions, expose configuration with two highly device specific management interfaces with 8 resp. 4 overloaded instructions, and respond to a standardised save state interface with 4 overloaded instructions. - -Having a standardised overloadable interface simply avoids much of the need for isa extensions for hardware with non standard interfaces and semantics. This is analogous to the way that the standardised overloadable ioctl interface of the kernel almost completely avoids the need for extending the kernel with syscalls for the myriad of hardware devices with their specific interfaces and semantics. - -Since the rs1 input of the overloaded ext_ctl instruction's are taken by the interface cookie, they are restricted in use compared to a normal R-type instruction (it is possible to pass 12 bits of additional info by or ing it with the cookie). Delegation is also expected to come at a small additional performance price compared to a "native" instruction. This should be an acceptable tradeoff in most cases. - -The expanded flexibility comes at the cost: the standard can specify the semantics of the delegation mechanism and the interfacing with the rest of the cpu, but the actual semantics of the overloaded instructions can only be defined by the designer of the interface. Likewise, a device can be conforming as far as delegation and interaction with the CPU is concerned, but whether the hardware is conforming to the semantics of the interface is outside the scope of spec. Being able to specify that semantics using the methods used for RV itself is clearly very valuable. One impetus for doing that is using it for purposes of its own, effectively freeing opcode space for other purposes. Also, some interfaces may become de facto or de jure standards themselves, necessitating hardware to implement competing interfaces. I.e., facilitating a free for all, may lead to standards proliferation. C'est la vie. - -The only "ISA-collisions" that can still occur are in the 20 bit (~10^6) interface identifier space, with 12 more bits to identify a device on a hart that implements the interface. One suggestion is setting aside 2^19 id's that are handed out for a small fee by a central (automated) registration (making sure the space is not just claimed), while the remaining 2^19 are used as a good hash on a long, plausibly globally unique human readable interface name. This gives implementors the choice between a guaranteed private identifier paying a fee, or relying on low probabilities. The interface identifier could also easily be extended to 42 bits on RV64. - - -====End RB== +NOTE: under discussion. This proposal basically mirrors the concept of POSIX ioctls, providing (arbitrarily) 8 functions (opcodes) whose meaning may be over-ridden @@ -363,141 +346,6 @@ in an object-orientated fashion by calling an "open handle" (and close) function (instruction) that switches (redirects) the 8 functions over to different opcodes. - -The "open handle" opcode takes a GUID (globally-unique identifier) -and an ioctl number, and stores the UUID in a table indexed by the -ioctl number: - - handle_global_state[8] # stores UUID or index of same - - def open_handle(uuid, ioctl_num): - handle_global_state[ioctl_num] = uuid - - def close_handle(ioctl_num): - handle_global_state[ioctl_num] = -1 # clear table entry - - -"Ioctls" (arbitrarily 8 separate R-type opcodes) then perform a redirect -based on what the global state for that numbered "ioctl" has been set to: - - def ioctl_fn0(*rargs): # star means "take all arguments as a tuple" - if handle_global_state[0] == CUSTOMEXT1UUID: - CUSTOMEXT1_FN0(*rargs) # apply all arguments to function - elif handle_global_state[0] == CUSTOMEXT2UUID: - CUSTOMEXT2_FN0(*rargs) # apply all arguments to function - else: - raise Exception("undefined opcode") - -=== RB == - -not quite I think. It is more like - -// Hardware, implementing interface with UUID 0xABCD - - def A_shutdown(cookie, data): - ... - - def A_init(data) - - def A_do_stuff(cookie, data): - ... - - def A_do_more_stuff(cookie, data): - ... - - interfaceA = { - "shutdown": A_shutdown, - "init": A_init, - "ctl0": A_do_stuff, - "ctl1": A_do_more_stuff - } - -// hardware implementing interface with UUID = 0x1234 - - def B_do_things(cookie, data): - ... - def B_shutdown(cookie, data) - ... - - interfaceB = { - "shutdown": B_shutdown, - "ctl0": B_do_things - } - - -// The CPU being wired to the devices - - cpu_interfaces = { - 0xABCD: interfaceA, - 0x1234: interfaceB - } - -// The functionality that the CPU must implement to use the extension interface - - cpu_open_handles = {} - - __handleId = 0 - def new_unused_handle_id() - __handleId = __handleId + 1 - return __handleId - - def ext_open(uuid, data): - interface = cpu_interface[uuid] - if interface == NIL: - raise Exception("No such interface") - - handleId = new_unused_handle_id() - cpu_open_handles[handleId] = (interface, CurrentVirtualMemoryAddressSpace) - - cookie = A_init(data) # Here device takes over - - return (handle_id, cookie) - - def ext_close(handle, data): - (handleId, cookie) = handle - intf_VMA = cpu_open_handles[handleId] - if intf_VMA == NIL: - return -1 - - (interface, VMA) = intf_VMA - if VMA != CurrentVirtualMemoryAddressSpace: - return -1 - assert(interface != NIL) - shutdown = interface["shutdown"] - if shutdown != NIL: - - err = interface.shutdown(cookie, data) # Here device takes over - - if err != 0: - return err - cpu_open_handles[handleId] = NIL - return 0 - - def ext_ctl0(handle, data): - (handleId, cookie) = handle - intf_VMA = cpu_open_handles[handleId] - if intf_VMA == NIL: - raise Exception("No such interface") - - (interface, VMA) = intf_VMA - if VMA != CurrentVirtualMemoryAddressSpace: - raise Exception("No such interface") #Disclosing that the interface exists in different address is security hole - - assert(interface != NIL) - ctl0 = interface["ctl0"] - if ctl0 == NIL: - raise Exception("No such Instruction") - - return ctl0(cookie, data) # Here device takes over - - -The other ext_ctl's are similar. - -==End RB== - - - - The proposal is functionally near-identical to that of the mvendor/march-id except extended down to individual opcodes. As such it could hypothetically be proposed as an independent Standard Extension in its own right that extends @@ -519,8 +367,6 @@ being worthwhile in its own right, and standing on its own merits and thus definitely worthwhile pursuing, it is non-trivial and much more invasive than the mvendor/march-id WARL concept. - - # Comments, Discussion and analysis TBD: placeholder as of 26apr2018 diff --git a/isa_conflict_resolution/ioctl.mdwn b/isa_conflict_resolution/ioctl.mdwn index 098d8aa95..7acd97bd5 100644 --- a/isa_conflict_resolution/ioctl.mdwn +++ b/isa_conflict_resolution/ioctl.mdwn @@ -1,123 +1,229 @@ -==introduction== +# ioctl-like + +==RB=== + +This proposal adds a standardised extension interface to the RV +instruction set by introducing a fixed small number (e.g. 8) of +"overloadable" R-type opcodes ext_ctl0, .. ext_ctl7. Each takes a process +local interface cookie in rs1. Based on the cookie, the CPU routes the +"overloaded" instructions to a "device" on or off the CPU that implements +the actual semantics. + +The cookie is "opened" with an additional r-type instruction ext_open that +takes a 20 bit identifier and "closed" with an ext_close instruction. The +implementing hardware device can use the cookie to reference internal +state. Thus, interfaces may be statefull. + +CPU's and devices may implement several interfaces, indeed, are expected +to. E.g. a single hardware device might expose a functional interface with +6 overloaded instructions, expose configuration with two highly device +specific management interfaces with 8 resp. 4 overloaded instructions, +and respond to a standardised save state interface with 4 overloaded +instructions. + +Having a standardised overloadable interface simply avoids much of the +need for isa extensions for hardware with non standard interfaces and +semantics. This is analogous to the way that the standardised overloadable +ioctl interface of the kernel almost completely avoids the need for +extending the kernel with syscalls for the myriad of hardware devices +with their specific interfaces and semantics. + +Since the rs1 input of the overloaded ext_ctl instruction's are taken +by the interface cookie, they are restricted in use compared to a normal +R-type instruction (it is possible to pass 12 bits of additional info by +or ing it with the cookie). Delegation is also expected to come at a small +additional performance price compared to a "native" instruction. This +should be an acceptable tradeoff in most cases. + +The expanded flexibility comes at the cost: the standard can specify the +semantics of the delegation mechanism and the interfacing with the rest +of the cpu, but the actual semantics of the overloaded instructions can +only be defined by the designer of the interface. Likewise, a device +can be conforming as far as delegation and interaction with the CPU +is concerned, but whether the hardware is conforming to the semantics +of the interface is outside the scope of spec. Being able to specify +that semantics using the methods used for RV itself is clearly very +valuable. One impetus for doing that is using it for purposes of its own, +effectively freeing opcode space for other purposes. Also, some interfaces +may become de facto or de jure standards themselves, necessitating +hardware to implement competing interfaces. I.e., facilitating a free +for all, may lead to standards proliferation. C'est la vie. -This proposal adds a standardised extension interface to the RV instruction set. +The only "ISA-collisions" that can still occur are in the 20 bit (~10^6) +interface identifier space, with 12 more bits to identify a device on +a hart that implements the interface. One suggestion is setting aside +2^19 id's that are handed out for a small fee by a central (automated) +registration (making sure the space is not just claimed), while the +remaining 2^19 are used as a good hash on a long, plausibly globally +unique human readable interface name. This gives implementors the choice +between a guaranteed private identifier paying a fee, or relying on low +probabilities. The interface identifier could also easily be extended +to 42 bits on RV64. -The extension consists of 2 + a fixed small number (we will assume 8) of R-type instructions. The main 8 instructions are "overloadable" R-type instructions ext_ctl0, .. ext_ctl7 that take a handle in rs1 consisting of a cpu determined, virtual-memory-address-space local interface id and a device determined cookie. More precisely, based on the interface id, the CPU routes the "overloaded" instructions to an on or off chip device that implements the actual semantics. The handle is created with an additional r-type instruction ext_open that takes a 20 bit UUID identifier and is "closed" with an ext_close instruction. The implementing hardware device can use the cookie to reference internal state. Thus, interfaces may be state-full. -CPU's and devices may implement several interfaces, indeed, are expected to. E.g. a single hardware device might expose a functional interface with 6 overloaded instructions, expose configuration with two highly device specific management interfaces with 8 resp. 4 overloaded instructions, and respond to a standardised save state interface with 4 overloaded instructions. +====End RB== -The following table shows the analogies: +This proposal basically mirrors the concept of POSIX ioctls, providing +(arbitrarily) 8 functions (opcodes) whose meaning may be over-ridden +in an object-orientated fashion by calling an "open handle" (and close) +function (instruction) that switches (redirects) the 8 functions over to +different opcodes. -posix RV Extension interface -long open(const char* device_interface) lui rd <20bit-hash of device_interface_name>; ext_open rd rd zero -long open(cons char* hw_device) lui rd <20bit-hash of device_interface_name>; ori rd rd <12 bit deviceId>; ext_open rd rd zero -int close(int fd) ext_close rd rs1 zero -long ioctl(int fd, 0, long data) ext_ctl0 rd rs1 rs2 -long ioctl(int fd, 1, long data) ext_ctl1 rd rs1 rs2 -long ioctl(int fd, 2, long data) ext_ctl2 rd rs1 rs2 +The "open handle" opcode takes a GUID (globally-unique identifier) +and an ioctl number, and stores the UUID in a table indexed by the +ioctl number: + + handle_global_state[8] # stores UUID or index of same + def open_handle(uuid, ioctl_num): + handle_global_state[ioctl_num] = uuid -Since the rs1 input of the overloaded ext_ctl instruction's are taken by the interface cookie, they are restricted in use compared to a normal R-type instruction (it is possible to pass 12 bits of additional info by or ing it with the cookie). Delegation is also expected to come at a small additional performance price compared to a "native" instruction. This should be an acceptable tradeoff in most cases. + def close_handle(ioctl_num): + handle_global_state[ioctl_num] = -1 # clear table entry + + +"Ioctls" (arbitrarily 8 separate R-type opcodes) then perform a redirect +based on what the global state for that numbered "ioctl" has been set to: + + def ioctl_fn0(*rargs): # star means "take all arguments as a tuple" + if handle_global_state[0] == CUSTOMEXT1UUID: + CUSTOMEXT1_FN0(*rargs) # apply all arguments to function + elif handle_global_state[0] == CUSTOMEXT2UUID: + CUSTOMEXT2_FN0(*rargs) # apply all arguments to function + else: + raise Exception("undefined opcode") + +=== RB == -The expanded flexibility comes at the cost: the standard can specify the semantics of the delegation mechanism and the interfacing with the rest of the cpu, but the actual semantics of the overloaded instructions can only be defined by the designer of the interface. Likewise, a device can be conforming as far as delegation and interaction with the CPU is concerned, but whether the hardware is conforming to the semantics of the interface is outside the scope of spec. Being able to specify that semantics using the methods used for RV itself is clearly very valuable. One impetus for doing that is using it for purposes of its own, effectively freeing opcode space for other purposes. Also, some interfaces may become de facto or de jure standards themselves, necessitating hardware to implement competing interfaces. I.e., facilitating a free for all, may lead to standards proliferation. C'est la vie. +not quite I think. It is more like -The only "ISA-collisions" that can still occur are in the 20 bit (~10^6) interface identifier space, with 12 more bits to identify a device on a hart that implements the interface. One suggestion is setting aside 2^19 id's that are handed out for a small fee by a central (automated) registration (making sure the space is not just claimed), while the remaining 2^19 are used as a good hash on a long, plausibly globally unique human readable interface name. This gives implementors the choice between a guaranteed private identifier paying a fee, or relying on low probabilities. The interface identifier could also easily be extended to 42 bits on RV64. +// Hardware, implementing interface with UUID 0xABCD + def A_shutdown(cookie, data): + ... -The whole extension consists of 10 R-type instructions, ext_open, ext_close ext_ctl0, ext_ctl1, ext_ctl7 that mimic the device interface for posix The number of 8 ext_ctl instructions is arbitrary and open to debate. + def A_init(data) -Encoding is TBD but it is intended that the instructions are in the regular OP segment of the encoding, NOT in one reserved for experimentation or future extensions since the point of the + def A_do_stuff(cookie, data): + ... + def A_do_more_stuff(cookie, data): + ... -== Description of the instructions == + interfaceA = { + "shutdown": A_shutdown, + "init": A_init, + "ctl0": A_do_stuff, + "ctl1": A_do_more_stuff + } -ext_open rd rs1 rs2 +// hardware implementing interface with UUID = 0x1234 -Opens am extension device implementing some extension interface. + def B_do_things(cookie, data): + ... + def B_shutdown(cookie, data) + ... --- rs1 contains a XLEN length number whose bits 12..31 that are an UIID that identifies the interface (recommended practice is either a registered number or of a good hash function over a long human readable plausibly unique interface name) -The low 12 bits enumerate the devices implementing this interface on the current hart (e.g. a low_power slow and high_power fast or connected to different periferals). + interfaceB = { + "shutdown": B_shutdown, + "ctl0": B_do_things + } --- rs2 contains unspecified data that may be required to properly initialise the device. -After execution +// The CPU being wired to the devices ---if the cpu does not support the device (in particular, not support the interface if the low 12 bits of rs1 are zero), rd == 0, otherwise ---if the device did not successfully initialise, rd == a positive error code < (1 << 12), otherwise ---rd == a device handle, a nonzero number with bit 0,..11 zero, 12..XLEN-1 identifying an initialised device + implementation defined cookie to be used by the device e.g. to identify possible resource state. + cpu_interfaces = { + 0xABCD: interfaceA, + 0x1234: interfaceB + } +// The functionality that the CPU must implement to use the extension interface -The ext_open instruction never traps. + cpu_open_handles = {} -The following sequence the device is can be used to ensure a device implementing an interface is properly initialised + __handleId = 0 + def new_unused_handle_id() + __handleId = __handleId + 1 + return __handleId - li t0 <20-bit UUID> - ext_open t0 t0 rs2 - li t1 (1 << 12) - bltu t0 t1 L_fail + def ext_open(uuid, data): + interface = cpu_interface[uuid] + if interface == NIL: + raise Exception("No such interface") -//use t0 with ext_ctl's + handleId = new_unused_handle_id() + cpu_open_handles[handleId] = (interface, CurrentVirtualMemoryAddressSpace) -We can use c.li instead of li if the error code is guaranteed to be less than (1<<5) and beqz if the interface is guaranteed to not fail on initialisation. + cookie = A_init(data) # Here device takes over -All the devices implementing an interface (with a non failing close) can be enumerated with the following sequence + return (handle_id, cookie) - lui t1 <20-bit UUID> -Loop_begin: - ext_open t0 t1 zero - beqz t0 Loop_end + def ext_close(handle, data): + (handleId, cookie) = handle + intf_VMA = cpu_open_handles[handleId] + if intf_VMA == NIL: + return -1 - //use t0 with ext_ctl's - ... - ext_close zero t0 zero - add a5 a5 1 - j Loop_begin: -Loop_end: + (interface, VMA) = intf_VMA + if VMA != CurrentVirtualMemoryAddressSpace: + return -1 + assert(interface != NIL) + shutdown = interface["shutdown"] + if shutdown != NIL: ------------------- + err = interface.shutdown(cookie, data) # Here device takes over -ext_close rd rs1 rs2 + if err != 0: + return err + cpu_open_handles[handleId] = NIL + return 0 -invalidate the extension handle, and release the extension device and the resources associated to the the handle obtained with ext_open. + def ext_ctl0(handle, data): + (handleId, cookie) = handle + intf_VMA = cpu_open_handles[handleId] + if intf_VMA == NIL: + raise Exception("No such interface") --- rs1 contains any number --- rs2 contains unspecified data that may be necessary to deinitialise the device + (interface, VMA) = intf_VMA + if VMA != CurrentVirtualMemoryAddressSpace: + raise Exception("No such interface") #Disclosing that the interface exists in different address is security hole -After execution: --- if rs1 contains an opened extension device and the device did not successfully close, the handle remains valid and rd == negative error code, --- if rs1 contains an opened extension device and the device successfully closed, all resources are released, the handle is invalidated,and rd == 0 --- if rs1 does not contain an opened extension device handle, rd == 0 + assert(interface != NIL) + ctl0 = interface["ctl0"] + if ctl0 == NIL: + raise Exception("No such Instruction") -It follows that ext_close does not trap and is idempotent. + return ctl0(cookie, data) # Here device takes over -Remark: -Devices that do not exhaust resources may not need closing. ------------------- +The other ext_ctl's are similar. -ext_ctl0 rd rs1 rs2 -ext_ctl1 rd rs1 rs2 -.... +==End RB== -ext_ctl7 rd rs1 rs2 -Execute some operation on the extension device. The number of ext_ctl instructions is open to debate. --- rs1 contains an opened extension handle, optionally or'ed with a 12 bit unsigned number --- rs2 constains unspecified data -After execution of ext_ctl[[i]] --- If rs1 is not an opened device handle, the instruction traps. --- If rs1 is an open device handle but the instruction is not defined in the interface, the instruction traps. --- If rs1 is an open device handle and the instruction is defined by the interface, a device is identified by the device handle and the operation ctl[[i]] of the device is called with the low twelve bits and the cookie of the handle on input port1, the value of rs2 on input port2 and rd as the output port. +The proposal is functionally near-identical to that of the mvendor/march-id +except extended down to individual opcodes. As such it could hypothetically +be proposed as an independent Standard Extension in its own right that extends +the Custom Opcode space *or* fits into the brownfield spaces within the +existing ISA opcode space *or* is used as the basis of an independent +Custom Extension in its own right. -Remark: -For a device that never fails, is always available and that does not requiring closing +==RB== +I really think it should be in browncode +==RB== + +One of the reasons for seeking an extension of the Custom opcode space is +that the Custom opcode space is severely limited: only 2 opcodes are free +within the 32-bit space, and only four total remain in the 48 and 64-bit +space. + +Despite the proposal (which is still undergoing clarification) +being worthwhile in its own right, and standing on its own merits and +thus definitely worthwhile pursuing, it is non-trivial and much more +invasive than the mvendor/march-id WARL concept. -lui rd <20bit UUID of the Frobate interface> -ext_open rd rd rs1 -ext_ctl0 rd rd rs2 -can be macro op fused to an internal instruction frobate_ctl0 rd rs1 rs2. Effectively this introduces a 2 input 1 output register instruction without claiming opcode space. The sequence will be well-defined on cpu's not implementing the Frobate instruction but trap. on the ext_ctl0 instruction because because after the ext_open rd == 0. .