- Introduction
- Compiling the libcpm3-z80
- Compiling your CP/M program
- Advanced libcpm3-z80 features
- What is implemented?
- To Do
libcpm3-z80 is a portable, readable, and minimal ISO/IEC 9899:TC2 Standard C Library for Digital Research's CP/M 3.
Implementing a standard library shouldn't be a daunting task. Unless the library immoderately aspires to target all architectures and all compilers by using sophisticated preprocessor directives and tricks, only known to god and a few earthlings.
libcpm3-z80 is an attempt to provide a library, written in purest C, with a clear separation of platform independent and platform dependent code. To port it to your architecture you need to provide a handful of well documented platform specific functions that the standard requires and are not available in CP/M's BDOS.
You need a Linux with the latest version of SDCC development tools installed.
Then get the repository by executing
git clone https://github.com/tstih/libcpm3-z80.git --recurse-submodules
This will download libcpm3-z80 and submodule libsdcc-z80.
After that you can compile a basic version of libcpm3-z80 by issuing a make
command in root folder.
Make will produce three files in the bin
folder.
crt0cpm3-z80.rel
This is the C runtime start-up file.libcpm3-z80.lib
This is the CP/M 3 standard C library.libsdcc-z80.lib
This is the SDCC bare metal library.
You need to link all three with your CP/M program.
Check the Makefile
in the hello
sample folder.
The library was designed for CP/M 3, and uses BDOS system calls to implement most features. But some are not covered by the BDOS. An example is reading and writing system time. The library still implements almost complete time.h
. By providing your own platform dependant functions you can unlock full features of the libcpm3-z80.
To compile the platform dependant library add your platform name to the arguments.
make PLATFORM=partner
The value
partner
is an example, use the name of your platform instead.
The system will then ignore the existing basic (proxy!) platform dependant code and expect that the missing functions are found at link time in another library or object file.
If platform dependant functions are not linked with your program the linker will not be able to resolve them and will throw errors.
The platform dependant functions are defined with empty bodies by the libcpm3-z80. If you compile a basic libcpm3-z80 version you should not only avoid platform dependant functions, but also following standard C because they depend on platform dependant functions.
Header | Platform dependant function |
---|---|
time.h | clock() |
time.h | time() |
stdarg.h | argv[0] |
If you compile with the make PLATFORM=<name>
command then
the empty functions are not compiled with the library.
You can provide implementations for these functions in another library or object file and link with your program. Here is a list of functions you need to provide.
/* Non standard function to get system date and time.
Declared in: time.h */
extern int gettimeofday(struct timeval *tv);
/* Non standard function to set system date and time
Declared in: time.h*/
extern int settimeofday(const struct timeval *tv);
/* Sleep in milliseconds
Declared in: unistd.h */
extern void msleep(int millisec);
/* Called just after the libcpm3-z80 library initializes!
Implement this to initialize your own libraries at the same time.
Declared in: stdlib.h */
extern void libinit();
There is also one platform dependant variable: progname
, defined
in stdlib.h
. By default it is empty, but if you implement the
libinit()
and set it to name of your running program, then the
argv[0]
will be populated correctly. Otherwise the argv[0]
will be an empty string. Other arguments are picked up from the
program tail correctly.
We just didn't know how to implement the
argv[0]
safely. If you do please send us a message via github Issues.
Click on the header name to see the scope of its implementation.
sys/bdos.h/
/* The usual exit codes. */
#define BDOS_SUCCESS 0
#define BDOS_FAILURE 0xff
/* CP/M 3 BDOS function codes
* See here: https://www.seasip.info/Cpm/bdos.html
*/
/* Process commands */
#define P_TERMCPM 0 /* Process terminate */
#define P_CODE 108 /* Set process return code */
/* Console commands */
#define C_READ 1 /* Console read */
#define C_WRITE 2 /* Console write */
#define C_RAWIO 6 /* Console raw input */
#define C_DELIMIT 110 /* Get or set delimiter $ */
#define C_WRITEBLK 111 /* Send block of text to console */
/* Drive commands */
#define DRV_SET 14 /* Set drive */
#define DRV_LOGINVEC 24 /* Enumerate drives */
#define DRV_GET 25 /* Get current drive */
#define DRV_DPB 31 /* Get drive info block */
/* File commands */
#define F_OPEN 15 /* Open file */
#define F_CLOSE 16 /* Close file */
#define F_READ 20 /* Read from file */
#define F_WRITE 21 /* Write to file */
#define F_MAKE 22 /* Create file */
#define F_DMAOFF 26 /* Set DMA offset */
#define F_USERNUM 32 /* Get/set user area */
#define F_READRAND 33 /* Read random record */
#define F_WRITERAND 34 /* Write random record */
#define F_SIZE 35 /* Get file size in blocks */
#define F_RANDREC 36 /* Store current pos. */
#define F_TRUNCATE 99 /* Truncate a file */
#define F_PARSE 152 /* Parse filename into FCB. */
/* BDOS return code */
typedef struct bdos_ret_s {
uint8_t reta; /* return code in reg A */
uint8_t retb; /* return code in reg B */
uint16_t rethl; /* return code in HL */
} bdos_ret_t;
/* Hadrware errors, returned in the H register. */
#define HWERR_SOFTWARE 0 /* Software error (i.e. file not found)*/
#define HWERR_DRV_SEL 1 /* Cant access drive */
#define HWERR_DISK_RO 2 /* Disk is read only */
#define HWERR_FILE_RO 3 /* File is read only */
#define HWERR_INV_DRV 4 /* Invalid drive */
#define HWERR_FOPEN 5 /* File is already open */
#define HWERR_CHECKSUM 6 /* FCB checksum error */
#define HWERR_PASSWORD 7 /* Password error */
#define HWERR_FEXISTS 8 /* File already exists */
#define HWERR_INV_FNME 9 /* Name contains ? */
/* Call bdos, return register A. */
extern uint8_t bdos(uint8_t fn, uint16_t param);
/* Call bdos, return results. */
extern bdos_ret_t *bdosret(uint8_t fn, uint16_t param, bdos_ret_t *p);
sys/stat.h/
struct stat {
char st_drive; /* A - P */
uint8_t st_user; /* 0 - 15 */
off_t st_size; /* Total size, in bytes */
uint16_t st_blksize; /* Block size */
uint16_t st_blocks; /* Number of blocks */
uint8_t st_lrb; /* Last record byte count */
};
/* Read file stat. */
extern int stat(char *pathname, struct stat *statbuf);
sys/types.h/
/* This should be signed size, but it is too short. */
typedef long ssize_t;
/* Used to represent file sizes. */
typedef long off_t;
ctype.h/
/* True if char is a letter. */
extern bool isalpha(int c);
/* True if char is white space. */
extern bool isspace(int c);
/* True if char is punctuation. */
extern bool ispunct(int c);
/* True if char is a digit */
extern bool isdigit(int c);
/* Returns char, converted to lowercase. */
extern int tolower(int c);
/* Return char converted to uppercase */
extern int toupper(int c);
errno.h/
#define ENOENT 2 /* no such file or directory */
#define EIO 5 /* I/O error */
#define E2BIG 7 /* argument list too long */
#define EBADF 9 /* bad file descriptor */
#define EAGAIN 11 /* try again */
#define EWOULDBLOCK EAGAIN /* -"- */
#define ENOMEM 12 /* out of memory */
#define EINVAL 22 /* negative offset or offset beyond end of file? Invalid address */
#define ENFILE 23 /* too many open files (file table overflow) */
#define ENOTTY 25 /* not a typewriter */
#define EPIPE 32 /* broken pipe */
/* global error code */
extern int errno;
fcntl.h/
#define O_RDONLY 0x0000 /* Read only. */
#define O_WRONLY 0x0001 /* Write only. */
#define O_RDWR 0x0002 /* Read and write. */
#define O_CREAT 0x0200 /* Create if nonexistant */
#define O_TRUNC 0x0400 /* Truncate to zero length */
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
/* Open file, return file descriptor. */
extern int open(const char *pathname, int flags);
/* Create file, return file descriptor. */
extern int creat(const char *pathname);
float.h/
#define FLT_RADIX 2
#define FLT_MANT_DIG 24
#define FLT_EPSILON 1.192092896E-07F
#define FLT_DIG 6
#define FLT_MIN_EXP (-125)
#define FLT_MIN 1.175494351E-38F
#define FLT_MIN_10_EXP (-37)
#define FLT_MAX_EXP (+128)
#define FLT_MAX 3.402823466E+38F
#define FLT_MAX_10_EXP (+38)
limits.h/
#define CHAR_BIT 8 /* Bits in char. */
#define SCHAR_MIN -128
#define SCHAR_MAX +127
#define UCHAR_MAX 255
#define CHAR_MIN -128
#define CHAR_MAX +127
#define MB_LEN_MAX 8 /* Max. bytes in multi byte char. */
#define SHRT_MIN -128
#define SHRT_MAX +127
#define USHRT_MAX 255
#define INT_MIN -32768
#define INT_MAX +32767
#define UINT_MAX 65535
#define LONG_MIN -2147483648
#define LONG_MAX +2147483647
#define ULONG_MAX 4294967295
#define SSIZE_MAX 65535 /* Max bytes for file read */
math.h/
/* constants */
#define HUGE_VAL 3.402823466e+38
/* functions */
extern float ceil(float x);
extern float cos(float x);
extern float cot(float x);
extern float exp(float x);
extern float fabs(float x);
extern float frexp(float x, int *pw2);
extern float floor(float x);
extern float ldexp(float x, int pw2);
extern float log(float x);
extern float log10(float x);
extern float modf(float x, float * y);
extern float pow(float x, float y);
extern float sin(float f);
extern float sqrt(float x);
extern float tan(float x);
stdarg.h/
/* Standard C var arg macros */
#define va_list unsigned char *
#define va_start(marker, last) { marker = (va_list)&last + sizeof(last); }
#define va_arg(marker, type) *((type *)((marker += sizeof(type)) - sizeof(type)))
#define va_end(marker) marker = (va_list) 0;
stdbool.h/
#define bool int
#define false 0
#define FALSE 0
#define true 1
#define TRUE 1
stddef.h/
typedef uint16_t ptrdiff_t; /* Result of sub. two pointers. */
typedef uint16_t size_t; /* sizeof type */
stdint.h/
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef int int16_t;
typedef unsigned int uint16_t;
typedef long int32_t;
typedef unsigned long uint32_t;
stdio.h/
#define EOF 0x1A
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
/* FILE type. */
typedef struct _iobuf {
char flags[4];
int fd;
bool eof;
} FILE;
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
/* Open file. */
extern FILE *fopen(const char *path, const char *mode);
/* Close a file. */
extern int fclose(FILE *fp);
/* Write a record. */
extern size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);
/* Read a record. */
extern size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
/* Move to fpos. */
extern int fseek(FILE *fp, long offset, int whence);
/* EOF reached? */
extern int feof(FILE *fp);
/* Get file position. */
extern long ftell(FILE *fp);
/* Get char. */
extern int fgetc(FILE *fp);
/* Prints a string. */
extern int puts(const char *s);
/* Reads a string */
extern char *gets(char *str);
/* Print formatted string to stdout. */
extern int printf(char *fmt, ...);
/* Prints formated string to a string. */
extern int sprintf(char *buf, char *fmt, ...);
/* Prints a char. */
extern int putchar(int c);
/* Reads a char (blocks). */
extern int getchar(void);
stdlib.h/
/* Standard requires it here. */
#ifndef NULL
#define NULL 0
#endif /* NULL */
/* How console functions interpret \n? As \n or as \r\n? */
#define NL_LF 0
#define NL_CRLF 1
#define NL_LFCR 2
extern char nltype;
/* Non standard extension, the name of the platform on
which library was build i.e. z80-none or z80-partner.
This is changed when adding PLATFORM=name to make call. */
extern char *libplatform;
/* Non standard extension, running program name.
Used for argv[0]. */
extern char *progname;
/* Exit application. */
extern void exit(int status);
/* Absolute value. */
extern int abs (int i);
/* Covert ascii to integer. */
extern int atoi(const char *str);
/* Convert integer to ascii. */
extern char *itoa(int num, char *str, int base);
/* Return random number */
extern int rand(void);
/* Set random seed. */
extern void srand(unsigned int seed);
/* String to long using base. */
extern long strtol(char *nptr, char **endptr, int base);
/* String to unsigned long using base, */
extern unsigned long strtoul(const char *nptr, char **endptr, int base);
/* Memory allocation. */
extern void *malloc(size_t size);
/* Allocate zero initialized block. */
extern void *calloc (size_t num, size_t size);
/* Free allocated memory block. */
extern void free(void *ptr);
/* Quick sort */
extern void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
/* Non standard extension, this is a hook, called just
after intialization of the Standard library */
extern void libinit();
/* Non standard extension: path parser.
Supported path formats are:
[<drive>:]filename.typ[[g]<user area>]
Returns 0 for success */
#define MAX_DRIVE 1
#define MAX_FNAME 8
#define MAX_EXT 3
extern int splitpath(
const char *path,
char *drive,
int *user,
char *fname,
char *ext
);
string.h/
#ifndef NULL
#define NULL ( (void *) 0)
#endif /* NULL */
/* Set n bytes in memory block to the value c, */
extern void *memset(void *s, int c, size_t n);
/* Copy memory block, */
extern void *memcpy(const void *dest, const void *src, size_t n);
/* Searches for the first occurrence of the character c
(an unsigned char) in the first n bytes. */
extern void *memchr(const void *s, int c, size_t n);
/* Compare memory blocks. 0=equal. */
extern int memcmp(const void *s1, const void *s2, size_t n);
/* Zero terminated string length. */
extern size_t strlen(const char *s);
/* Copy string to another string */
extern char *strcpy(char *dest, const char *src);
/* Copy max num chars to another string. */
extern char* strncpy(char* dst, const char* src, size_t num);
/* Compare strings, 0=match. */
extern int strcmp(const char *s1, const char *s2);
/* Compare first n chars of string. */
extern int strncmp(const char *s1, const char *s2, size_t n);
/* Find first occurence of c in s. */
extern char *strchr(const char *s, int c);
/* Find first occurence of c in s frin the right (=last occurence). */
extern char *strrchr(const char *s, int c);
/* Reverse a string. */
extern void strrev(char *s, register size_t n);
/* Non standard (unix) tokenizer. */
extern char *strsep(char **stringp, const char *delim);
/* The length of the number of characters before the 1st occurrence
of character present in both the string. */
extern size_t strcspn(const char *s1, const char *s2);
/* Tokenize string s using delimiters delim. */
extern char *strtok(char *s, const char *delim);
/* Return text of the errnum system error. */
extern char *strerror(int errnum);
/* Non standard extension: stoupper */
extern void stoupper(char *s);
/* Non standard extension: stolower */
extern void stolower(char *s);
time.h/
/* idp clock has a resolution of 1/100 sec */
#define CLOCKS_PER_SEC 100
typedef long clock_t;
/* Std C defines this as number of seconds since 00:00, Jan 1 1970 UTC */
typedef long time_t;
/* Std C tm struct */
struct tm {
/* seconds, range 0 to 59 */
int tm_sec;
/* minutes, range 0 to 59 */
int tm_min;
/* hours, range 0 to 23 */
int tm_hour;
/* day of the month, range 1 to 31 */
int tm_mday;
/* month, range 0 to 11 */
int tm_mon;
/* The number of years since 1900 */
int tm_year;
/* day of the week, range 0 to 6 */
int tm_wday;
/* day in the year, range 0 to 365 */
int tm_yday;
/* daylight saving time */
int tm_isdst;
};
/* non standard for settimeofday and gettimeofday functions */
struct timeval {
time_t tv_sec; /* seconds since Jan. 1, 1970 */
int tv_msec; /* and milliseconds */
};
/* Converts given calendar time tm to a textual representation of
the following fixed 25-character form: Www Mmm dd hh:mm:ss yyyy. */
extern char* asctime(const struct tm* time_ptr);
/* Return current clock in 1/1000 seconds */
extern clock_t clock(void);
/* Convert current time to textual representation using the following
format Www Mmm dd hh:mm:ss yyyy (uses asctime...).*/
extern char* ctime(const time_t* ptt);
/* Returns difference between two time points in seconds! */
extern long difftime(time_t time_end,time_t time_beg);
/* Get Greenwich mean time (politically correct: UTC), make localtime
equal to UTC. */
#define localtime gmtime
extern struct tm *gmtime(const time_t *timer);
/* Create time_t given tm structure. */
extern time_t mktime(struct tm *tme);
/* Get current time. */
extern time_t time(time_t *arg);
/* Non standard function to get system date and time. */
extern int gettimeofday(struct timeval *tv);
/* Non standard function to set system date and time */
extern int settimeofday(const struct timeval *tv);
unistd.h/
/* Posix read. */
extern ssize_t read(int fd, void *buf, size_t count);
/* Posix close. */
extern int close(int fd);
/* Posix flush. */
extern int fsync(int fd);
/* Posix write. */
ssize_t write(int fd, const void *buf, size_t count);
/* Posix lseek function */
off_t lseek(int fd, off_t offset, int whence);
/* Non standard function to sleep (in milliseconds).
NOTE: The libcpm3-z80 only provides an empty proxy
to this function. If you want to use it you need to
compile the libcpm3-z80 with the PLATFORM switch and
inject your own implementation. */
extern void msleep(int millisec);
Following functions and variables in libcpm3-z80 are not part of the Standard C library.
Header | Function |
---|---|
sys/bdos.h | bdos(), bdosret() |
sys/stat.h | stat() |
time.h | gettimeofday(), settimeofday() |
unistd.h | msleep(), lseek(), close(), read(), write() |
fcntl.h | open(), creat(), fcntl() |
stdlib.h | libplatform, libinit(), splitpath(), nl |
string.h | stoupper(), stolower(), strrev() |
dirent.h
has no implementation.
CP/M 3 Plus has date and time sys calls and the library should use them as default implementation.