ASMLIB - z88dk/z88dk GitHub Wiki

ASMLIB - SDCC library for assembler and UNAPI interop v1.0

Originally by Konamiman, 2/2010

1. Introduction

ASMLIB is a C library that allows to execute Z80 assembler code from C applications. It is mainly intended for accessing MSX BIOS routines, MSX-DOS functions and UNAPI implementations, but can be used to execute any arbitrary assembler code.

ASMLIB is provided as a ready to use library for the SDCC compiler, but the source code is provided as well so that it can be adapted for other C compilers as well. This includes Z88DK's Classic SCCZ80 compiler.

2. Usage

Using ASMLIB in your C programs is easy. All you have to do is add an #include <arch/z80.h> line to your source code.

3. Data structures reference

The data structures explained here are defined in the file arch/z80.h.

3.1 The Z80 registers structure (Z80_registers)

The ASMLIB functions allow to specify the values of the Z80 registers before executing assembler code, and also to retrieve the register values after the code execution. A data structure named Z80_registers that holds the registers values is used for this.

Z80_registers has members to specify the values of registers AF, BC, DE, HL, IX and IY. For flexibility, it is defined as an union of four different structures that allow to access the registers at different levels of detail. These structures are:

  • Bytes: allows to access the single byte registers A, F, B, C, D, E, H, L, IXh, IXl, IYh, and IYl. For example: regs.Bytes.A
  • Words: allows to access the register pairs AF, BC, DE, HL, IX, IY as signed two-byte integers. For example: regs.Words.HL
  • UWords: same as Words, but the registers are treated as unsigned two-byte integers.
  • Flags: allows to access the individual flags C, N, PV, H, Z and S of the F register (unused bits can be also accessde via "bit3" and "bit5" members). For example: regs.Flags.Z

Note that all four structs actually refer to the same data, so for example doing "regs.Bytes.H=0x11; regs.Bytes.L=0x22;" is the same as doing "regs.Words.HL=0x1122;".

3.2 The Z80 registers usage enumeration (register_usage)

When invoking assembler code, not all registers may be always needed to pass parameters in and out. The register_usage enumeration is used to specify which registers must be passed from Z80_registers to the executed code, and which registers must be retrieved back to Z80_registers. Four register usage levels are available:

  • REGS_NONE: No registers at all.
  • REGS_AF: Register pair AF only.
  • REGS_MAIN: Register pairs AF, BC, DE, and HL.
  • REGS_ALL: Register pairs AF, BC, DE, HL, IX and IY.

When invoking an assembler routine repeatedly in a loop, specifying a restricted register usage level can make a slight difference on execution time.

3.3 The UNAPI code block (unapi_code_block)

This structure is used when interacting with UNAPI implementations.

UNAPI implementations can expose their entry point at three different location types: at ROM, at a mapped RAM segment, or at page 3 RAM. Depending on the location type, the code needed to execute the UNAPI routines is different: a CALSLT call is needed for ROM, a call to one of the RAM helper routines is needed for mapped RAM, and a simple CALL instruction is needed for page 3.

The unapi_code_block structure holds two small pieces of assembler code that are specifically tailored to interact with a given UNAPI implementation. It consists of two byte arrays:

  • UnapiCallCode: assembler code to execute the routine whose identifier is passed in register A.
  • UnapiReadCode: assembler code to read one byte of data from the implementation space. Data address is passed in register pair HL and the obtained data is returned in register A.

The contents of the unapi_code_block structure for a given UNAPI implementation can be generated by executing the UnapiBuildCodeBlock function. From that point, the UnapiCall and UnapiRead functions can be used to interact with the implementation; these two functions take a unapi_code_block structure as input parameter.

4. Functions reference

4.1 AsmCall

  void AsmCall(
       uint address,
       Z80_registers* regs,
       register_usage inRegistersDetail,
       register_usage outRegistersDetail)

This is the main function of ASMLIB, all other functions end up calling this one. It allows to execute an assembler routine placed at any address of the Z80 addressing space.

The routine is invoked with the registers in the state specified by the registers structure passed ("regs"); which fields of the registers structure are actually passed to the assembler routine is specified by the "inRegistersDetail" parameter.

After the assembler routine returns, the state of the Z80 registers is copied back to "regs"; this time, which registers are actually copied back is specified by the "outRegistersDetail" parameter.

4.2 DosCall

  void DosCall(
        byte function,
        Z80_registers* regs,
        register_usage inRegistersDetail,
        register_usage outRegistersDetail)

This function is a simple wrapper for the AsmCall function that allows to execute MSX-DOS functions from the MSX-DOS environment. It simply puts the function number in register C, then uses AsmCall to invoke the DOS entry point at address 0x0005.

If inRegistersDetail is REGS_NONE or REGS_AF, it will be converted to REGS_MAIN prior to invoking AsmCall, since register C must always be passed to the DOS entry point.

Note that if you want to execute MSX-DOS functions from within the MSX-BASIC environment, you can't use DosCall. Instead, you need to manually set the function number in register C ("regs.Bytes.C=function;"), and then use AsmCall to invoke the DOS entry point at address 0xF37D.

4.3 BiosCall

  void BiosCall(
        uint address,
        Z80_registers* regs,
        register_usage outRegistersDetail)

This function is a simple wrapper for the AsmCall function that allows to execute BIOS routines from the MSX-DOS environment. It simply puts the routine address at IX and the BIOS slot at IYh, then executes the standard CALSLT routine.

There is no parameter for specifying the input registers usage level. This is because IX and IY are always used, so REGS_ALL is always assumed.

Note that when running in the MSX-BASIC environment you don't need to use this function, you can use AsmCall directly instead.

4.4 UnapiGetCount

  int UnapiGetCount(
       char* implIdentifier)

This function returns the number of UNAPI implementations of a given UNAPI specification currently available on the system. UNAPI clients should use this function at the beginnin of the application to ensure that there is actually any suitable UNAPI implementation installed.

implIdentifier is a pointer to a standard C string. It can be an inline string, for example: UnapiGetCount("ETHERNET");

4.5 UnapiBuildCodeBlock

  void UnapiBuildCodeBlock(
        char* implIdentifier,
        int implIndex,
        unapi_code_block* codeBlock)

This function will build a code block suitable for interacting with the UNAPI implementation of the specified identifier (implIdentifier) which has the specified index (implIndex). This code block is intended to be passed as an input parameter to functions UnapiCall, UnapiRead, and UnapiParseCodeBlock.

implIdentifier is a pointer to a standard C string. It can be an inline string, for example: UnapiBuildCodeBlock("ETHERNET", ...). It can also be NULL; in this case, the specification identifier is assumed to be already at ARG (0xF847). This is useful when UnapiBuildCodeBlock is invoked immediately after UnapiGetCount, which always sets ARG appropriately.

The behavior of this function when an invalid UNAPI implementation is referenced (implIndex is zero or greater than the number of implementations available for the given implIdentifier) is undefined.

4.6 UnapiParseCodeBlock

  void UnapiParseCodeBlock(
        unapi_code_block* codeBlock,
        byte* slot,
        byte* segment,
        uint* address)

Usually, UNAPI client applications do not need to actually know where an UNAPI implementation is located. Building a unapi_code_block structure with UnapiBuildCodeBlock and passing it to UnapiCall or UnapiRead is enough for most purposes.

However, occasionally it may be convenient to know where an UNAPI implementation is located, for example to show this information to the user. In this case, the UnapiParseCodeBlock function can be used. It parses the contents of the passed unapi_code_block structure (which must have been previously filled by using UnapiBuildCodeBlock) and returns the location (slot, segment, and entry point address) of the associated implementation.

The obtained information must be interpreted according to the UNAPI rules, that is:

  • If "address" is a page 3 address (>=0xC000), then it is a page 3 implementation; "slot" and "segment" must be ignored.
  • Otherwise, if "segment" is 0xFF, it is a ROM implementation.
  • Otherwise, it is a mapped RAM implementation.

The data returned by this function when codeBlock has not been properly built is undefined.

4.7 UnapiGetRamHelper

  void UnapiGetRamHelper(
        uint* jumpTableAddress,
        uint* mapperTableAddress)

When using the ASMLIB functions to interact with UNAPI implementations, it is not necessary to directly interact with the RAM helper, since the code placed on the unapi_code_block structures used take care of it already. However, occasionally it may be useful to retrieve the location of the UNAPI RAM helper jump table address and mapper table address, for example for robustness (to check that the RAM helper is actually installed when interacting with an implementation on mapped RAM) or to display information about the available mapped RAM.

This function will simply return the location of the RAM helper jump table address and mapper table address. When no RAM helper is installed, jumpTableAddress will be zero.

If only the jump table address is needed and we don't care about the mapper table address, then "mapperTableAddress" can be NULL, and vice versa.

4.8 UnapiCall

  void UnapiCall(
        unapi_code_block* codeBlock,
        byte functionNumber,
        Z80_registers* registers,
        register_usage inRegistersDetail,
        register_usage outRegistersDetail)

This function allows to invoke the routines of an UNAPI implementation. The target UNAPI implementation is identified by an unapi_code_block structure (which has to have been previously build with UnapiBuildCodeBlock), and the function number is passed as a parameter. "registers", "inRegistersDetail" and "outRegistersDetail" have the same meaning as in function AsmCall.

If inRegistersDetail is REGS_NONE, it will be converted to REGS_AF prior to invoking the routine call, since register A is always used to hold the UNAPI function number.

If codeBlock has not been previously initialized with UnapiBuildCodeBlock, the result is unpredictable.

4.9 UnapiRead

  byte UnapiRead(
        unapi_code_block* codeBlock,
        uint address)

This function allows to read one byte from the space of an UNAPI implementation. The target UNAPI implementation is identified by an unapi_code_block structure (which has to have been previously build with UnapiBuildCodeBlock), and the address to read is passed as a parameter. The returned value is the readed byte.

This function is mainly intended to read the implementation name whose pointer is returned by UNAPI function 0, but may be used to read other data that the implementation may offer as well.

If codeBlock has not been previously initialized with UnapiBuildCodeBlock, the result is unpredictable.

5. Sample programs

Together with the ASMLIB library, the source code of one example application is provided.

C-TYPE.C is a simple program that reads one file and displays its contents on the screen, much like the COMMAND.COM command "type" but replacing non-printable control characters into dots. All the file and screen access is done by executing MSX-DOS functions, using the DosCall function.

Note that these samples need to be compiled by using the alternate startup code "crt0msx_msxdos_advanced", which is available at Konamiman's home page.

6. More information & contact

For more information about the UNAPI specification in general and about the Ethernet UNAPI specification, and to find more great (huh?) MSX software, please visit Konamiman's MSX page at: http://www.konamiman.com

To get a handful of useful resources for MSX development using SDCC, visit Avelino Herrera's MSX page at: http://msx.atlantes.org/index_en.html

And of course, you can contact me at: [email protected]

⚠️ **GitHub.com Fallback** ⚠️