MICROSEQ(9)

πŸ“¦
version 15.1 Β· 2 versions
, in freebsd 14.5, freebsd 15.1, nixpkgs unstable

πŸ¦‹ MICROSEQ(9) πŸ¦‹

okay bestie, so this is the Kernel Developer's Manual page for microseq β€” the ppbus microsequencer developer's guide. yep, we're getting into it. grab a snack πŸ“


✨ SYNOPSIS ✨

like, include these or it's not gonna work, no cap:

#include <sys/types.h>
#include <dev/ppbus/ppbconf.h>
#include <dev/ppbus/ppb_msq.h>

πŸ’– DESCRIPTION πŸ’–

so first things first β€” go read ppbus(4) for the ppbus description and all the general info about the microsequencer. it's giving prerequisites, iykyk.

the whole point of this document, bestie, is to encourage developers to actually use the microsequencer mechanism so that we get:

  1. a uniform programming model
  2. efficient code

before you go off and use microsequences, you're encouraged (read: strongly encouraged, do it πŸ’…) to look at the ppc(4) microsequencer implementation, and also at an example of how to use it in ppi(4). literally go look at them. they ate. πŸ”₯


πŸŽ€ PPBUS register model πŸŽ€

🌸 Background 🌸

the parallel port model chosen for ppbus is the PC parallel port model. so any register we describe later has the exact same semantic as its counterpart in a PC parallel port β€” same vibes, same behaviour. for more info about ISA/ECP programming, go get the Microsoft standard referenced as "Extended Capabilities Port Protocol and ISA interface Standard". yes, that's its whole name. iconic of them.

the registers described later are standard parallel port registers.

mask macros are defined in the standard ppbus include files for each valid bit of the parallel port registers. so like, they're already there for you, mwah. 🫢


πŸ¦‹ Data register πŸ¦‹

in compatible or nibble mode, writing to this register will drive data to the parallel port data lines. in any other mode, drivers may be tri-stated by setting the direction bit (PCD) in the control register. reads to this register return the value on the data lines. simple as that, periodt.


πŸ’œ Device status register πŸ’œ

this read-only register reflects the inputs on the parallel port interface. read-only means read-only, bestie, don't try it πŸ™ˆ

  • bit 7 β€” nBUSY β€” inverted version of parallel port Busy signal
  • bit 6 β€” nACK β€” version of parallel port nAck signal
  • bit 5 β€” PERROR β€” version of parallel port PERROR signal
  • bit 4 β€” SELECT β€” version of parallel port Select signal
  • bit 3 β€” nFAULT β€” version of parallel port nFault signal

others are reserved and return undefined result when read. so like, don't count on them, they're mystery bits πŸ’€


⭐ Device control register ⭐

this register directly controls several output signals as well as enabling some functions. main character register tbh πŸ‘‘

  • bit 5 β€” PCD β€” direction bit in extended modes
  • bit 4 β€” IRQENABLE β€” 1 enables an interrupt on the rising edge of nAck
  • bit 3 β€” SELECTIN β€” inverted and driven as parallel port nSelectin signal
  • bit 2 β€” nINIT β€” driven as parallel port nInit signal
  • bit 1 β€” AUTOFEED β€” inverted and driven as parallel port nAutoFd signal
  • bit 0 β€” STROBE β€” inverted and driven as parallel port nStrobe signal

(and yes bestie, there's more β€” this was just part 1, breathe 🧚)

πŸ› οΈ MICROINSTRUCTIONS πŸ› οΈ

✨ Description ✨

okay so every microinstruction is one of these four vibes: a parallel port access, a program iteration, a submicrosequence, or a C call. and the parallel port? bestie, you have to think of it as the logical model described over in ppbus(4). no cap.

the microinstructions you actually get are these, and yes i'm giving you the whole list because i love you:

#define MS_OP_GET       0       /* get <ptr>, <len>                     */
#define MS_OP_PUT       1       /* put <ptr>, <len>                     */
#define MS_OP_RFETCH    2       /* rfetch <reg>, <mask>, <ptr>          */
#define MS_OP_RSET      3       /* rset <reg>, <mask>, <mask>           */
#define MS_OP_RASSERT   4       /* rassert <reg>, <mask>                */
#define MS_OP_DELAY     5       /* delay <val>                          */
#define MS_OP_SET       6       /* set <val>                            */
#define MS_OP_DBRA      7       /* dbra <offset>                        */
#define MS_OP_BRSET     8       /* brset <mask>, <offset>               */
#define MS_OP_BRCLEAR   9       /* brclear <mask>, <offset>             */
#define MS_OP_RET       10      /* ret <retcode>                        */
#define MS_OP_C_CALL    11      /* c_call <function>, <parameter>       */
#define MS_OP_PTR       12      /* ptr <pointer>                        */
#define MS_OP_ADELAY    13      /* adelay <val>                         */
#define MS_OP_BRSTAT    14      /* brstat <mask>, <mask>, <offset>      */
#define MS_OP_SUBRET    15      /* subret <code>                        */
#define MS_OP_CALL      16      /* call <microsequence>                 */
#define MS_OP_RASSERT_P 17      /* rassert_p <iter>, <reg>              */
#define MS_OP_RFETCH_P  18      /* rfetch_p <iter>, <reg>, <mask>       */
#define MS_OP_TRIG      19      /* trigger <reg>, <len>, <array>        */

🧠 Execution context

so when a microinstruction runs, this is the little world it lives in:

  • the program counter, which points at the next microinstruction to execute β€” could be in the main microsequence, could be in a subcall 🎯
  • the current value of ptr, which points at the next char to send/receive
  • the current value of the internal branch register

heads up: some of the microinstructions mess with this data. not all of them. don't @ me.

πŸ“₯ MS_OP_GET and MS_OP_PUT

these are the microinstructions you use to do either predefined standard IEEE1284-1994 transfers or programmed non-standard io. that's the whole gig πŸ’…

πŸ” MS_OP_RFETCH - Register FETCH

grabs the current value of a parallel port register, slaps a mask on it, and saves it in a buffer. iconic behavior honestly.

Parameters:

  1. register
  2. character mask
  3. pointer to the buffer

Predefined macro: MS_RFETCH(reg,mask,ptr)

πŸŽ›οΈ MS_OP_RSET - Register SET

asserts and/or clears some bits of a particular parallel port register β€” two masks get applied, so you're doing both at once, multitasking queen.

Parameters:

  1. register
  2. mask of bits to assert
  3. mask of bits to clear

Predefined macro: MS_RSET(reg,assert,clear)

πŸ’ͺ MS_OP_RASSERT - Register ASSERT

asserts all the bits of a particular parallel port register. all of them. every last one.

Parameters:

  1. register
  2. byte to assert

Predefined macro: MS_RASSERT(reg,byte)

⏳ MS_OP_DELAY - microsecond DELAY

delays the execution of the microsequence. that's it, that's the whole personality.

Parameter:

  1. delay in microseconds

Predefined macro: MS_DELAY(delay)

πŸ”’ MS_OP_SET - SET internal branch register

sets the value of the internal branch register. lowkey important.

Parameter:

  1. integer value

Predefined macro: MS_SET(accum)

πŸ” MS_OP_DBRA - Do BRAnch

branches if the internal branch register, decremented by one, gives a positive result value. yes, it decrements and branches, it's a whole thing.

Parameter:

  1. integer offset in the current executed (sub)microsequence. the offset is added to the index of the next microinstruction to execute.

Predefined macro: MS_DBRA(offset)

🟒 MS_OP_BRSET - BRanch on SET

branches if some of the status register bits of the parallel port are set.

Parameter:

  1. bits of the status register
  2. integer offset in the current executed (sub)microsequence. the offset is added to the index of the next microinstruction to execute.

Predefined macro: MS_BRSET(mask,offset)

βšͺ MS_OP_BRCLEAR - BRanch on CLEAR

branches if some of the status register bits of the parallel port are cleared.

Parameter:

  1. bits of the status register
  2. integer offset in the current executed (sub)microsequence. the offset is added to the index of the next microinstruction to execute.

Predefined macro: MS_BRCLEAR(mask,offset)

🏁 MS_OP_RET - RETurn

returns from a microsequence. and bestie, this one is mandatory β€” it's literally the only way the microsequencer can tell the microsequence is over. do not skip it, do not pass go. the return code gets handed back in the integer pointed at by the (int *) parameter of ppb_MS_microseq().

Parameter:

  1. integer return code

Predefined macro: MS_RET(code)

πŸ“ž MS_OP_C_CALL - C function CALL

calls C functions from microsequence execution. super handy when you're doing some non-standard i/o to pull a data character off the parallel port.

Parameter:

  1. the C function to call
  2. the parameter to pass to the function call

The C function shall be declared as a int(*)(void *p, char *ptr). The ptr parameter is the current position in the buffer currently scanned.

Predefined macro: MS_C_CALL(func,param)

πŸ“ MS_OP_PTR - initialize internal PTR

initializes the internal pointer to the currently scanned buffer. this pointer is what gets passed to any C call (see above πŸ‘†).

Parameter:

  1. pointer to the buffer that shall be accessed by xxx_P() microsequence calls. note that this pointer is automatically incremented during xxx_P() calls

Predefined macro: MS_PTR(ptr)

😴 MS_OP_ADELAY - do an Asynchronous DELAY

makes a tsleep() happen during microsequence execution. the tsleep runs at PPBPRI level. nap time but make it kernel 🫢

Parameter:

  1. delay in ms

Predefined macro: MS_ADELAY(delay)

🚦 MS_OP_BRSTAT - BRanch on STATe

branches based on the status register state condition.

Parameter:

  1. mask of asserted bits. bits that shall be asserted in the status register are set in the mask
  2. mask of cleared bits. bits that shall be cleared in the status register are set in the mask
  3. integer offset in the current executed (sub)microsequence. the offset is added to the index of the next microinstruction to execute.

Predefined macro: MS_BRSTAT(asserted_bits,clear_bits,offset)

↩️ MS_OP_SUBRET - SUBmicrosequence RETurn

returns from the submicrosequence call. this action is mandatory before a RET call β€” like, seriously. and some microinstructions (PUT, GET) may not be callable within a submicrosequence. so watch out.

No parameter.

Predefined macro: MS_SUBRET()

πŸ“² MS_OP_CALL - submicrosequence CALL

calls a submicrosequence. and a submicrosequence is just a microsequence with a SUBRET call in it β€” that's the definition, that's the whole lore.

Parameter:

  1. the submicrosequence to execute

Predefined macro: MS_CALL(microseq)

🎯 MS_OP_RASSERT_P - Register ASSERT from internal PTR

asserts a register using the data that the internal PTR pointer is currently pointing at.

Parameter:

  1. amount of data to write to the register
  2. register

Predefined macro: MS_RASSERT_P(iter,reg)

πŸ“€ MS_OP_RFETCH_P - Register FETCH to internal PTR

fetches data from a register. the data lands in the buffer the internal PTR pointer is currently pointing at.

Parameter:

  1. amount of data to read from the register
  2. register
  3. mask applied to fetched data

Predefined macro: MS_RFETCH_P(iter,reg,mask)

⚑ MS_OP_TRIG - TRIG register

triggers the parallel port. this microinstruction exists to give you very efficient control of the parallel port β€” like, it's the whole point. triggering a register means: writing data, waiting a while, writing data, waiting a while… which is how you sneak magic sequences onto the port ✨

Parameter:

  1. amount of data to read from the register
  2. register
  3. size of the array
  4. array of unsigned chars. each couple of u_chars defines the data to write to the register and the delay in us to wait. the delay is limited to 255 us to keep things simple and shrink the array size.

Predefined macro: MS_TRIG(reg,len,array)


🌈 MICROSEQUENCES 🌈

🧱 C structures

union ppb_insarg {
     int     i;
     char    c;
     void    *p;
     int     (* f)(void *, char *);
};

struct ppb_microseq {
     int                     opcode;         /* microins. opcode */
     union ppb_insarg        arg[PPB_MS_MAXARGS];    /* arguments */
};

πŸ’« Using microsequences

to instantiate a microsequence you literally just declare an array of ppb_microseq structures and initialize it however you need. you can either use the predefined macros or code your microinstructions directly according to the ppb_microseq definition β€” your call, bestie. for example:

     struct ppb_microseq select_microseq[] = {

             /* parameter list
              */
             #define SELECT_TARGET    MS_PARAM(0, 1, MS_TYP_INT)
             #define SELECT_INITIATOR MS_PARAM(3, 1, MS_TYP_INT)

             /* send the select command to the drive */
             MS_DASS(MS_UNKNOWN),
             MS_CASS(H_nAUTO | H_nSELIN |  H_INIT | H_STROBE),
             MS_CASS( H_AUTO | H_nSELIN |  H_INIT | H_STROBE),
             MS_DASS(MS_UNKNOWN),
             MS_CASS( H_AUTO | H_nSELIN | H_nINIT | H_STROBE),

             /* now, wait until the drive is ready */
             MS_SET(VP0_SELTMO),
/* loop: */     MS_BRSET(H_ACK, 2 /* ready */),
             MS_DBRA(-2 /* loop */),
/* error: */    MS_RET(1),
/* ready: */    MS_RET(0)
     };

see how some of those parameters are undefined? yeah, those have to get filled in before you execute the microsequence. to initialize each microsequence, you should use the ppb_MS_init_msq() function, like this:

     ppb_MS_init_msq(select_microseq, 2,
                     SELECT_TARGET, 1 << target,
                     SELECT_INITIATOR, 1 << initiator);

and then you execute the microsequence. order matters, babe πŸ’…

🎬 The microsequencer

the microsequencer gets executed either at ppbus or adapter level (peep ppbus(4) if you want the tea on ppbus system layers). most of the microsequencer runs at ppc level specifically to dodge the ppbus-to-adapter function call overhead β€” efficiency queen. but some actions, like deciding whether the transfer is IEEE1284-1994 compliant, get executed at ppbus layer instead. iykyk.


πŸ‘€ SEE ALSO πŸ‘€

ppbus(4), ppc(4), ppi(4) β€” go read them, they're literally right there 🫢

πŸ“œ HISTORY πŸ“œ

the microseq manual page first appeared in FreeBSD 3.0. vintage ✨

πŸ’– AUTHORS πŸ’–

this manual page was written by Nicolas Souchu. thank you for your service king πŸ‘‘

πŸ’€ BUGS πŸ’€ β€” the fine print, bestie, read it

okay so before you go off and build your little microsequence empire, there's two things you need to know, and honestly? they're giving "your fave has limits" energy 😭

  • Only one level of submicrosequences is allowed. β€” like, yes, a microsequence can call a submicrosequence, we love a callback moment 🫢 but that submicro is not allowed to call another one. One level. That's it. Do not try to nest deeper, it's not gonna slay, it's just gonna break πŸ’€
  • When triggering the port, maximum delay allowed is 255 us. β€” so the delay you can ask for when you trigger that port maxes out at 255 us. Not more. That's the whole ceiling, no cap ⭐ go past it and things get real weird, and not in the fun way πŸ™ˆ

and that's the page, bestie!! πŸŽ€ you made it all the way through MICROSEQ(9), i'm literally so proud of you 🌸 this one's a Debian situation, dated June 6, 1998 β€” yeah, an iconic vintage era πŸ‘‘ lowkey a period piece, but the microcode still hits. now go forth and microsequence responsibly πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from