(no commit message)
[libreriscv.git] / isa_conflict_resolution / ioctl.mdwn
index 098d8aa9503a8742a950e12b112f81da86220a1f..8807e0cfc383e2365477538bae0ce1cf8fb38418 100644 (file)
-==introduction==
+# pluggable extensions 
 
-This proposal adds a standardised extension interface to the RV instruction set. 
+==RB===
 
-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.
+This proposal adds a standardised extension instructions to the RV
+instruction set by introducing a fixed small number N (e.g. N = 8) of
+R-type opcodes xcmd0 rd, rs1, rs2, .. , xcmd7 rd, rs1, rs2, that are intended to be used as "overloadable" (slightly crippled) R-type instructions for independently developed extensions in the form of non standard CPU extensions, IP tiles, or closely coupled external devices.
+Tl;DR see below for a C description of how this is supposed to work. 
+The input value of an xcmd instruction in rs2 is arbitrary. The content of the first input rs1, however, is divided in a 12bit "logical unit" (lun)  together with xlen - 12 bits of additional data. 
+The lun bits in rs1, determines a specific (sub)device, and the CPU routes the command to this device with rs1 and rs2 as input, and rd as output. Effectively, the xcmd0, ... xcmd7 instructions are "virtual method" opcodes, overloaded for different extension (sub)devices. 
 
-The following table shows the analogies:
+The specific value of the lun is supposed to be convenient for the cpu and is thus unstandardised. Portable software therefore constructs the lun, with a further R-type instruction xext. It takes a 20 bit universally unique identifier (UUID) that identifies  an interface with upto N R-type instructions with the signature of xcmd. An optional sequence number identifies a specific enumerated device on the cpu that implements the interface as a subdevice. For convenience, xext also or's bits rs2[0..XLEN-12]. If the UUID is not recognised 0 is returned. , but implemented by the extension (sub)device. Note that this scheme gives an easy work around the restriction on N (e.g. 8 ) commands: an implementing device can simply implement several interfaces as routable subdevices, indeed is expected to do so.  
 
-posix                                             RV Extension interface
+The net effect is that a sequence like 
 
-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
+    //fake UUID
+    lui   rd 0xEDCBA
+    xext  rd rd rs1
+    xcmd0 rd rd rs2 
 
+acts like a single namespaced instruction cmd0_EDCBA rd rs1 rs2 with the annoying caveat that rs1 can only use bits 0..XLEN-12 (the sequence is also not indivisible but the crucial semantics that you might want to be indivisible is in xcmd0). Delegation is expected to come at a small
+additional performance price compared to a "native" instruction. This should, however, be an acceptable tradeoff in many cases.
 
-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. 
-
-
-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. 
-
-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 
-
-
-== Description of the instructions ==
-
-ext_open  rd rs1 rs2 
-
-Opens am extension device implementing some extension interface. 
-
--- 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). 
-
--- rs2 contains unspecified data that may be required to properly initialise the device.
-
-After execution  
-
---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.
-
-
-The ext_open instruction never traps.
-
-The following sequence the device is can be used to ensure a device implementing an interface is properly initialised 
-
-     li t0 <20-bit UUID>
-     ext_open t0 t0 rs2
-     li t1 (1 << 12)
-     bltu t0 t1  L_fail 
-
-//use t0 with ext_ctl's
-
-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.
-
-All the devices implementing an interface (with a non failing close) can be enumerated with the following sequence
-
-    lui t1 <20-bit UUID>
-Loop_begin:
-    ext_open t0 t1 zero
-    beqz t0 Loop_end
-
-    //use t0 with ext_ctl's
+Programatically the instructions in the interface are just a set of glorified assembler macros
+
+    org.tinker.tinker:RocknRoll{
+        uuid : 0xABCDE
+        rock rd rs1 rs2 : xcmd0 rd rs1 rs2
+        roll rd rs1 rs2 : xcmd1 rd rs1 rs2
+    }
+
+so that the above sequence is more clearly written as 
+
+   import(org.tinker.tinker:RocknRoll)
+   lui rd org.tinker.tinker:RocknRoll:uuid
+   xext rd rd rs1
+   org.tinker.tinker:RocknRoll:rock
+    
+(Quite possibly even glorified standard assembler macros are overkill and it is easier to use defines or ordinary macro's with long names. E.g. writing 
+
+  #define org_tinker_tinker__RocknRoll__interface_uuid 0xABCDE 
+  #define org_tinker_tinker__RocknRoll__rock(rd, rs1, rs2) xcmd0 rd, rs1, rs2
+  #define org_tinker_tinker__RocknRoll__roll(rd, rs1, rs2) xcmd1 rd, rs1, rs2
+
+allows the same sequence to be written as
+
+   lui   rd org_tinker_tinker__RocknRoll__interface_uuid 
+   xext  rd rs1
+   org_tinker_tinker__RocknRoll__rock(rd, rd, rs2)
+
+Readability of assembler is no big deal for a compiler, but people are supposed to _document_ the interface and its semantics. In particular a semantics specified like the semantics of the cpu would be most welcome.)
+
+
+If several instructions of the same interface are used, one can also use instruction sequences like 
+   
+   lui   t1 org_tinker_tinker__RocknRoll__interface_uuid
+   xext  t1 zero
+   xcmd0 a5, t1, a0  // org_tinker_tinker__RocknRoll__rock(a5, t1, a0) 
+   xcmd1 t2, t1, a1  // org_tinker_tinker__RocknRoll__roll(t2, t1, a5)
+   xcmd0 a0, t1, t2  // org_tinker_tinker__RocknRoll__rock(a0, t1, t2)
+
+This amortises the cost of the xext instruction. 
+
+==Implications for the RiscV ecosystem ==
+
+
+The proposal allows independent groups to define one or more extension interfaces of (slightly crippled) R-type instructions 
+implemented by an extension device. Such an extension device would be an intrinsic non standard part of the CPU, an IP tile or a closely coupled external chip and would be configured at manufacturing time or bootup of the CPU.
+
+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. On RV64 the UUID can also be extended to 52 bits (> 10^15).
+
+
+==== Description of the extension as C functions.== 
+
+    /* register format of rs1 for xext instructions */
+    typedef struct uuid_device{
+      long dev:12;
+      long uuid: 8*sizeof(long) - 12;
+    } uuid_device_t
+
+    /* register format for rd of xext and rs1  for xcmd instructions, packs lun and data */
+    typedef struct lun_data{
+      long lun:12;
+      long data: 8*sizeof(long) - 12;
+    } lun_data_t
+
+    /* proposed R-type instructions 
+    xext  rd rs1 rs2
+    xcmd0 rd rs1 rs2
+    xcmd1 rd rs1 rs2
     ...
-    ext_close zero t0 zero     
-    add a5 a5 1
-    j Loop_begin:
-Loop_end:
-
-------------------
-
-ext_close rd rs1 rs2 
-
-invalidate the extension handle, and release the extension device and the resources associated to the the handle obtained with ext_open.  
-
--- rs1 contains any number 
--- rs2 contains unspecified data that may be necessary to deinitialise the device
-
-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
+    xcmd7 rd rs1 rs2
+    */
 
-It follows that ext_close does not trap and is idempotent. 
-
-Remark:
-Devices that do not exhaust resources may not need closing. 
-
-------------------
-
-ext_ctl0 rd rs1 rs2
-ext_ctl1 rd rs1 rs2
-....
-
-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. 
+    lun_data_t xext(uuid_dev_t rs1, long rs2);
+    long xcmd0(lun_data_t rs1, long rs2);
+    long xcmd1(lun_data_t rs1, long rs2);
+    ...
+    long xcmd<N>(lun_data_t rs1, long rs2);
 
-Remark:
-For a device that never fails, is always available and that does not requiring closing
+    /* hardware interface presented by an implementing device. */
+    typedef
+    long device_fn(unsigned short subdevice_xcmd, lun_data_t rs1, long rs2);
 
-lui rd <20bit UUID of the Frobate interface>
-ext_open rd rd rs1
-ext_ctl0 rd rd rs2
+    /* cpu internal datatypes */
 
-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.   . 
+    enum privilege = {user = 0b0001, super = 0b0010, hyper = 0b0100, mach = 0b1000};
 
+    /* cpu internal, does what is on the label */
+    static
+    enum privilege cpu__current_privilege_level()
+
+    typedef 
+    struct lun{
+      unsigned short id:12
+    } lun_t;
+
+    struct uuid_device_priv2lun{
+      struct{
+          uuid_dev_t    uuid_dev;
+          enum privilege reqpriv;
+      };
+      lun_t lun;
+    };
+
+    struct device_subdevice{
+      device_fn* device_addr;
+      unsigned short subdeviceId:12;
+    };      
+
+    struct lun_priv2device_subdevice{
+      struct{
+           lun_t lun;
+           enum privilege reqpriv
+      }
+      struct device_subdevice devAddr_subdevId;
+    }
+
+    static 
+    struct uuid_device_priv2lun cpu__lun_map[];
+
+    /* 
+       map (UUID, device, privilege) to a 12 bit lun, 
+       return (lun_t){0} on unknown or no access
+
+       does associative memory lookup and tests privilege.
+    */
+    static
+    lun_t cpu__lookup_lun(const struct uuid_device_priv2lun* lun_map, uuid_dev_t uuid_dev, enum privilege priv);
+
+
+
+    lun_data_t xext(uuid_dev_t rs1, long rs2)
+    {
+       lun_t lun = cpu__lookup_lun(lun_map, rs1, current_privilege_level());
+
+       return (lun_data_t){.lun = lun.id, .data = rs2 % (1<< (8*sizeof(long) - 12))}
+    }
+
+
+
+
+    struct lun_priv2device_subdevice cpu__device_subdevice_map[];
+
+    /* map (lun, priv)  to struct device_subdevice pair. 
+       For lun = 0, or unknown (lun, priv) pair, returns (struct device_subdevice){NULL,0} 
+    */
+    static
+    device_subdevice_t cpu__lookup_device_subdevice(const struct lun_priv2device_subdevice_map* dev_subdev_map, 
+                                                    lun_t lun, enum privileges priv);
+
+    /* functional description of the delegating xcmd0 .. xcmd7 instructions */
+    template<k = 0..N-1> //pretend this is C
+    long xcmd<k>(lun_data_t rs1, long rs2)
+    {
+        struct device_subdevice dev_subdev = cpu_lookup_device_subdevice(device_subdevice_map, rs1.lun, current_privilege());
+        if(dev_subdev.devAddr == NULL)
+          trap(“Illegal instruction”);
+     
+        return dev_subdev.devAddr(dev_subdev.subdevId | k << 12, rs1, rs2);
+    }
+
+
+
+Example:
+    #define COM_BIGBUCKS__FROBATE__INTERFACE_UUID 0xABCDE
+    #define ORG_TINKER_TINKER__ROCKNROLL_INTERFACE_UUID 0x12345
+    #define ORG_TINKER_TINKER__JAZZ_INTERFACE_UUID 0xD0B0D
+
+    com.bigbucks:Frobate{
+        uuid: COM_BIGBUCKS__FROBATE__INTERFACE_UUID
+        frobate rd rs1 rs2 : cmd0 rd rs1 rs2
+        foo     rd rs1 rs2 : cmd1 rd rs1 rs2
+        bar     rd rs1 rs2 : cmd1 rd rs1 rs2
+    }
+
+    org.tinker.tinker:RocknRoll{
+        uuid: ORG_TINKER_TINKER__ROCKNROLL_INTERFACE_UUID
+        rock rd rs1 rs2: cmd0 rd rs1 rs2
+        roll rd rs1 rs2: cmd1 rd rs1 rs2
+    }
+
+    long com_bigbucks__device1(short  subdevice_xcmd, lun_data_t rs1, long rs2)
+    {
+      switch(subdevice_xcmd) {
+      case 0 | 0 << 12  /* com.bigbucks:Frobate:frobate */     : return device1_frobate(rs1, rs2);
+      case 42| 0 << 12  /* com.bigbucks:FrobateMach:frobate    : return device1_frobate_machine_level(rs1, rs2);
+      case 0 | 1 << 12  /* com.bigbucks:Frobate:foo */         : return device1_foo(rs1, rs2);
+      case 0 | 2 << 12  /* com.bigbucks:Frobate:bar */         : return device1_bar(rs1, rs2);
+      case 1 | 0 << 12  /* org.tinker.tinker:RocknRoll:rock */ : return device1_rock(rs1, rs2);
+      case 1 | 1 << 12  /* org.tinker.tinker:RocknRoll:roll */ : return device1_roll(rs1, rs2);
+      default: trap(“hardware configuration error”);
+      }
+    }
+
+    org.tinker.tinker:Jazz{
+       uuid: ORG_TINKER_TINKER__JAZZ_INTERFACE_UUID 
+       boogy rd rs1 rs2: cmd0 rd rs1 rs2
+    }
+
+    long org_tinker_tinker__device2(short subdevice_xcmd,  lun_data_t rs1, long rs2)
+    {
+      switch(dev_cmd.interfId){
+      case 0  | 0 << 12 /* com.bigbucks:Frobate:frobate */: return device2_frobate(rs1, rs2);
+      case 0  | 1 << 12 /* com.bigbucks:Frobate:foo */    : return device2_foo(rs1, rs2);
+      case 0  | 2 << 12 /* com.bigbucks:Frobate:bar */    : return device2_foo(rs1, rs2);
+      case 1  | 0 << 12 /* org_tinker_tinker:Jazz:boogy */: return device2_boogy(rs1, rs2);
+      default: trap(“hardware configuration error”);      
+      }
+    }
+
+   /* struct lun2dev_subdevice_map[] */
+      dev_subdevice_map = {
+      //     {.lun = 0,   error and falls back to trapping xcmd 
+             {{.lun = 1, .priv = user},  .devAddr_interfId = {fallback,    0 /* ReturnZero  */}},
+             {{.lun = 1, .priv = super}, .devAddr_interfId = {fallback,    0 /* ReturnZero  */}},
+             {{.lun = 1, .priv = hyper}, .devAddr_interfId = {fallback,    0 /* ReturnZero  */}},
+             {{.lun = 1, .priv = mach},  .devAddr_interfId = {fallback,    0 /* ReturnZero  */}},
+             {{.lun = 2, .priv = user},  .devAddr_interfId = {fallback,    1 /* ReturnMinusOne*/}},
+             {{.lun = 2, .priv = super}, .devAddr_interfId = {fallback,    1 /* ReturnMinusOne*/}},
+             {{.lun = 2, .priv = hyper}, .devAddr_interfId = {fallback,    1 /* ReturnMinusOne*/}},
+             {{.lun = 2, .priv = mach},  .devAddr_interfId = {fallback,    1 /* ReturnMinusOne*/}},
+     //       .lun = 3 .. 7  reserved for other fallback RV interfaces
+     //       .lun = 8 .. 30 reserved as error numbers, c.li t1 31; bltu rd t1 L_fail tests errors
+     //      .lun = 31  reserved out of caution 
+             {{.lun = 32, .priv = user},  .devAddr_interfId = {device1, 0 /* Frobate  interface */}},
+             {{.lun = 32, .priv = super}, .devAddr_interfId = {device1, 0 /* Frobate  interface */}},
+             {{.lun = 32, .priv = hyper}, .devAddr_interfId = {device1, 0 /* Frobate  interface */}},
+             {{.lun = 32, .priv = mach},  .devAddr_interfId = {device1,42 /* Frobate  machine level interface */}},
+             {{.lun = 33, .priv = user},  .devAddr_InterfId = {device1, 1 /* RocknRoll interface */}},
+             {{.lun = 33, .priv = super}, .devAddr_InterfId = {device1, 1 /* RocknRoll interface */}},
+             {{.lun = 33, .priv = hyper}, .devAddr_InterfId = {device1, 1 /* RocknRoll interface */}},
+             {{.lun = 34, .priv = super}, .devAddr_interfId = {device2, 0 /* Frobate interface */}},
+             {{.lun = 34, .priv = hyper}, .devAddr_interfId = {device2, 0 /* Frobate interface */}},
+             {{.lun = 34, .priv = mach},  .devAddr_interfId = {device2, 0 /* Frobate interface */}},
+             {{.lun = 35, .priv = super}, .devAddr_interfId = {device2, 1 /* Jazz interface */}},
+             {{.lun = 35, .priv = hyper}, .devAddr_interfId = {device2, 1 /* Jazz interface */}},
+       }
+
+
+     /* struct uuid_dev2lun_map[] */  
+      lun_map = {     
+          {.uuid_devId = {ORG_RISCV__FALLBACK__RETURN_ZERO__INTERFACE_UUID , 0},    {.lun = 1, .priv = user | super | hyper | mach },   
+          {.uuid_devId = {ORG_RISCV__FALLBACK__RETURN_MINUSONE__INTERFACE_UUID, 0}, {.lun = 2, .priv = user | super | hyper | mach },
+          {.uuid_devId = {COM_BIGBUCKS__FROBATE__INTERFACE_UUID, 0}, {.lun = 32, .priv = },
+          {.uuid_devId = {COM_BIGBUCKS__FROBATE__INTERFACE_UUID, 1}, .lun = 34},        //sic!
+          {.uuid_devId = {ORG_TINKER_TINKER__ROCKNROLL__INTERFACE_UUID, 0}, .lun = 33},  //sic!
+          {.uuid_devId = {ORG_TINKER_TINKER__JAZZ__INTERFACE_UUID, 0}, .lun = 35}
+     }
+