MICROSEQ(9)
π¦ 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:
- a uniform programming model
- 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β1enables an interrupt on the rising edge ofnAck - 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:
- register
- character mask
- 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:
- register
- mask of bits to assert
- 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:
- register
- 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:
- 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:
- 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:
- 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:
- bits of the status register
- 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:
- bits of the status register
- 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:
- 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:
- the C function to call
- 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:
- pointer to the buffer that shall be accessed by
xxx_P()microsequence calls. note that this pointer is automatically incremented duringxxx_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:
- delay in ms
Predefined macro: MS_ADELAY(delay)
π¦ MS_OP_BRSTAT - BRanch on STATe
branches based on the status register state condition.
Parameter:
- mask of asserted bits. bits that shall be asserted in the status register are set in the mask
- mask of cleared bits. bits that shall be cleared in the status register are set in the mask
- 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:
- 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:
- amount of data to write to the register
- 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:
- amount of data to read from the register
- register
- 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:
- amount of data to read from the register
- register
- size of the array
- array of unsigned chars. each couple of
u_charsdefines 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
255us. β so the delay you can ask for when you trigger that port maxes out at255us. 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
- freebsd 14.5: base 14.5
- freebsd 15.1: base 15.1
- nixpkgs unstable: source
- Copyright (c) 1998, 1999, Nicolas Souchu
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms