Newb questions for developing OS for new target

Discussion about other targets
Post Reply
cyrusbuilt
New member
Posts: 4
Joined: Mon Jan 13, 2025 8:15 pm

Newb questions for developing OS for new target

Post by cyrusbuilt »

Hello all!

I'll get this out of the way first: I'm a lil bit of a newb here.

I've designed and built my own homebrew board and would like to (at some point) build my own target. I call it "CyBorg" (more info here: https://github.com/cyrusbuilt/CyBorg and the relevant firmware here: https://github.com/cyrusbuilt/CyBorg-Northbridge). The core of the system is nearly identical to the Z80-MBC2 (more info here: https://github.com/SuperFabius/Z80-MBC2 and here: https://hackaday.io/project/159973-z80- ... 0-computer). I didn't see a target for the Z80-MBC2, but if there is one, let know because I could just modify that to suit my needs.

But ultimately, I'm attempting to develop my own OS for it and I'm having trouble setting up my dev environment. I'm using VSCode and even though I've added z88dk to the include paths, intellisense isn't working and I'm getting weird compilation results. I'll just start with the relevant files.

First, my c_cpp_properties.json file:

Code: Select all

{
    "configurations": [
        {
            "name": "Mac",
            "includePath": [
                "${default}",
                "${workspaceFolder}/**",
                "~/z88dk/include/**"
            ],
            "defines": [],
            "macFrameworkPath": [
                "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
            ],
            "compilerPath": "/opt/homebrew/bin/sdcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "gcc-arm64"
        }
    ],
    "version": 4
}
I'm almost certain I have a something goofy in my Makefile. I've only written a few of them and I don't have a whole lot of experience with the z88dk toolchain.

Makefile

Code: Select all

CC=zcc
AS=zcc
TARGET=+embedded
VERBOSITY=-vn
CRT=1

PRAGMA_FILE=zpragma.inc

C_OPT_FLAGS=-clib=new

CFLAGS=$(TARGET) $(VERBOSITY) $(C_OPT_FLAGS) -pragma-include:$(PRAGMA_FILE)
LDFLAGS=$(CFLAGS)
ASFLAGS=$(TARGET) $(VERBOSITY) -c

EXEC=kernel.bin
EXEC_OUTPUT=cyos

OBJECTS=Common/io.o Kernel/kernel.o

%.o: %.c $(PRAGMA_FILE)
	$(CC) $(CFLAGS) -o build/$@ $<

%.o: %.asm
	$(AS) $(ASFLAGS) -o build/$@ $<

all : $(EXEC)

$(EXEC) : $(OBJECTS)
	$(CC) $(LDFLAGS) -startup=$(CRT) $(OBJECTS) -o build/$(EXEC_OUTPUT) -create-app

.PHONY: clean

clean:
	rm -rf build

And now for the relevant sources:

Common/io.h

Code: Select all

#ifndef _IO_H
#define _IO_H

#include <sys/compiler.h>

void k_outp(unsigned int port, unsigned int val);
unsigned int k_inp(unsigned int port);

__sfr __at 0x00 EXEC_PORT;
__sfr __at 0x01 STORE_OPC_PORT;
__sfr __at 0x01 SER_RX_PORT;

#endif
Intellisense isn't picking up the '__sfr' symbol. But the compiler doesn't complain. I originally also used 'uint_fast8_t' but it didn't recognize that type even though I had #include'd <stdint.h>. So I switched to 'unsigned int' for now. Also, whenever I compile, it would complain uint_fast8_t was already defined somehow (I didn't write my own definition) whenever I include <stdint.h> or <stdlib.h> or <z80.h>. I don't get it. If I don't include stdint.h, the compiler says that type is undefined, but if I *do* include it, then it complains its *already* defined. What?

Common/io.c

Code: Select all

#include "io.h"

void k_outp(unsigned int port, unsigned int val) {
    STORE_OPC_PORT = port;
    EXEC_PORT = (char)(val);
}

unsigned int k_inp(unsigned int port) {
    return SER_RX_PORT;
}
I wrote my own inp() outp() implementation because I didn't think z80_inp() and z80_outp() would necessarily know how my virtual I/O engine in firmware works and as you'll see next, the same goes for getchar() and putchar() for the serial console.

Code: Select all

Kernel/kernel.h

#ifndef _KERNEL_H
#define _KERNEL_H

#include <stdlib.h>
#include <z80.h>

#define VERSION "0.1"

#define PANIC(fmt, ...)                                                        \
    do {                                                                       \
        printf("PANIC: %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__);  \
        while (1) {}                                                           \
    } while (0)
#endif

Kernel/kernel.c

Code: Select all

#include "kernel.h"
#include "opcodes.h"
#include "Common/io.h"

#define LF 0x0a
#define CR 0x0d
#define SER_BUF_EMPTY_BIT 0x04

int putchar(int c) {
    if (c == LF) {
        k_outp(OP_IO_WR_SER_TX, CR);
    }

    k_outp(OP_IO_WR_SER_TX, c);
    return c;
}

int getchar(void) {
    static char sysFlags;
    do {
        STORE_OPC_PORT = OP_IO_RD_SYSFLG;
        sysFlags = EXEC_PORT;
    } while (!(sysFlags & SER_BUF_EMPTY_BIT));
    return SER_RX_PORT;
}

/**
 * Kernel bootstrap routine.
 */
void kernel_main(void) {
    printf("\n\n");

    // Just something to verify we can boot and run.
    PANIC("%s: hello world!", VERSION);
}

/**
 * Kernel entry point.
 */
int main() {
    // TODO initialize subsystems

    // Should never return.
    kernel_main();
    return 0;
}

So with everything above (beyond the intellisense issues), it fails to compile with the following error:

Code: Select all

zcc +embedded -vn -clib=new -pragma-include:zpragma.inc -o build/Kernel/kernel.o Kernel/kernel.c
Kernel/kernel.c:38: warning: operator '##' produced the invalid token ',VERSION'
Kernel/kernel.c:32:11: warning: Implicit definition of function 'printf' it will return an int. Prototype it explicitly if this is not what you want. [-Wimplicit-function-definition]
Kernel/kernel.c::putchar::0::0:11: error: undefined symbol: _k_outp
  ^---- _k_outp
Kernel/kernel.c::putchar::0::0:11: error: undefined symbol: _k_outp
  ^---- _k_outp
Kernel/kernel.c::kernel_main::0::4:31: error: undefined symbol: _printf
  ^---- _printf
Kernel/kernel.c::kernel_main::0::4:31: error: undefined symbol: _printf
  ^---- _printf
make: *** [Kernel/kernel.o] Error 1
I suspected the issue was with the order of the files in $(OBJECTS) so I swapped them and got this instead:

Code: Select all

zcc +embedded -vn -clib=new -pragma-include:zpragma.inc -o build/Common/io.o Common/io.c
/Users/cyrus/z88dk/lib/config/../..//libsrc/_DEVELOPMENT/target/z80/z80_crt.asm.m4:1237: error: undefined symbol: _main
  ^---- _main
make: *** [Common/io.o] Error 1
Which feels like a chicken-and-egg scenario. Is there an issue with the order or folder structure? FUZIX (see https://github.com/EtchedPixels/FUZIX) has a target for Z80-MBC2 so I thought about reading through the sources and trying to reverse engineer how that target works and maybe try to adapt it to how z88dk works. I don't know. I haven't done enough in assembly or this low-level C work yet (and I'm pretty new to z88dk) to really know what I'm doing just yet. Here's my current zpragma.inc file too for completeness, though I'm sure it probably isn't quite right either:

zpragma.inc

Code: Select all

#pragma output CRT_MODEL = 1     // ROM model
#pragma output CRT_ORG_CODE = 0x0000 // move code origin
#pragma output CRT_ORG_DATA = 0x8000  // DATA appends to CODE
#pragma output CRT_ORG_BSS = 0   // BSS appends to DATA
My thought was since this is going to be an OS, maybe I'd want the ROM model. And I'm not sure I got ORG DATA right either. Just like Z80-MBC2, I have 4 banks of 32KB or RAM for a total of 128K (though I'd like to increase that at some point), but switching banks requires executing an opcode. I'm not sure what the best way to go about this is, nor am I completely sure about the CRT model just yet. Any help or information you fine folk could provide would be most helpful.
User avatar
dom
Well known member
Posts: 2269
Joined: Sun Jul 15, 2007 10:01 pm

Re: Newb questions for developing OS for new target

Post by dom »

So many queries!

Picking one to solve at random, the pre-processor doesn't support everything, but this works and gets rid of that tokenisation error and generates the right thing:

Code: Select all

#define PANIC(fmt, ...)                                                        \
    do {                                                                       \
        printf("PANIC: %s:%d: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__);  \
        while (1) {}                                                           \
    } while (0)
And another one:

Code: Select all

zcc +embedded -vn -clib=new -pragma-include:zpragma.inc -o build/Common/io.o Common/io.c
To generate an object file you need the -c option otherwise it will try to create a complete executable (and then complain that it can't find main or some other symbol)

Intellisense:

I suspect that sdcc as you've configured it isn't outputting diagnostics in a format that VScode expects. People have got CLion working in the past using clang as the "Intellisense" parser, but it does need a little bit of training - see https://github.com/z88dk/z88dk/issues/2511 and __sfr needs special treatment as well unfortunately, something along the lines of:

Code: Select all

#ifdef __INTELLISENSE__
extern unsigned char SER_RX_PORT
#else
__sfr __at 0x01 SER_RX_PORT;
#endif
One thing I will say is that bringing up a new target using newlib is (IMO) very hard. I've never got the hang of it and find it much easier to use the classic library.

So, I should recommend that you read: https://github.com/z88dk/z88dk/wiki/Classic--Homebrew and switch over - it walks you through the initial setup which is pretty much what you're trying to do.
cyrusbuilt
New member
Posts: 4
Joined: Mon Jan 13, 2025 8:15 pm

Re: Newb questions for developing OS for new target

Post by cyrusbuilt »

Thank you! That was most helpful.

I now have the following in Common/io.c

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <z80.h>
#include "Common/io.h"
#include "Kernel/opcodes.h"

#define LF 0x0a
#define CR 0x0d
#define SER_BUF_EMPTY_BIT 0x04

void k_outp(unsigned int port, unsigned int val) {
    z80_outp(port, val);
}

unsigned int k_inp(unsigned int port) {
    return z80_inp(port);
}

int fputc_cons_native(char c) __naked {
    if (c == LF) {
        STORE_OPC_PORT = OP_IO_WR_SER_TX;
        EXEC_PORT = (char)(CR);
    }

    STORE_OPC_PORT = OP_IO_WR_SER_TX;
    EXEC_PORT = c;
    return c;
}

int fgetc_cons(void) __naked {
    static char sysFlags;
    do {
        STORE_OPC_PORT = OP_IO_RD_SYSFLG;
        sysFlags = EXEC_PORT;
    } while (!(sysFlags & SER_BUF_EMPTY_BIT));
    return SER_RX_PORT;
}
Now when I compile I get the following:

Code: Select all

zcc +embedded -vn -c -SO3 --max-allocs-per-node200000 -compiler sdcc -clib=sdcc_iy -pragma-include:zpragma.inc -o Common/io.o Common/io.c
zcc +embedded -vn -clib=sdcc_iy -pragma-include:zpragma.inc -startup=1 Kernel/kernel.o Common/io.o -o kernel -create-app
stdio/z80/asm_puts_unlocked.asm:42: error: undefined symbol: _stdout
  ^---- (_stdout)
stdio/z80/asm_vprintf_unlocked.asm:48: error: undefined symbol: _stdout
  ^---- (_stdout)
make: *** [kernel.bin] Error 1
So I'm assuming the target doesn't define _stdout and I need to define it myself. I'm not exactly sure how to go about doing that. And if _stdout isn't defined, I'm assuming I'll also need to define _stdin somehow as well.
cyrusbuilt
New member
Posts: 4
Joined: Mon Jan 13, 2025 8:15 pm

Re: Newb questions for developing OS for new target

Post by cyrusbuilt »

Another thing that is happening is if I change the k_inp() and k_outp() definitions to use 'uint16_t' and 'uint8_t' instead of 'unsigned int', I get the following compiler error:

Code: Select all

zcc +embedded -vn -c -SO3 --max-allocs-per-node200000 -compiler sdcc -clib=sdcc_iy -pragma-include:zpragma.inc -o Common/io.o Common/io.c
Common/io.c:14: error 98: conflict with previous declaration of 'k_inp' for attribute 'type' at ./Common/io.h:8
from type 'unsigned-char function ( unsigned-char fixed) fixed'
  to type 'unsigned-char function ( unsigned-int fixed) fixed'
make: *** [Common/io.o] Error 1
And this happens even if I include <stdint.h>.
User avatar
dom
Well known member
Posts: 2269
Joined: Sun Jul 15, 2007 10:01 pm

Re: Newb questions for developing OS for new target

Post by dom »

cyrusbuilt wrote: Wed Jan 15, 2025 7:44 pm Another thing that is happening is if I change the k_inp() and k_outp() definitions to use 'uint16_t' and 'uint8_t' instead of 'unsigned int', I get the following compiler error:

Code: Select all

zcc +embedded -vn -c -SO3 --max-allocs-per-node200000 -compiler sdcc -clib=sdcc_iy -pragma-include:zpragma.inc -o Common/io.o Common/io.c
Common/io.c:14: error 98: conflict with previous declaration of 'k_inp' for attribute 'type' at ./Common/io.h:8
from type 'unsigned-char function ( unsigned-char fixed) fixed'
  to type 'unsigned-char function ( unsigned-int fixed) fixed'
make: *** [Common/io.o] Error 1
The way I can reproduce this is if the prototype doesn't match the function, for example:

Code: Select all

#include <stdint.h>
#include <z80.h>


// io.h

extern void k_outp(uint16_t, uint16_t);
extern uint16_t k_inp(uint16_t);



// io.c

void k_outp(unsigned int port, unsigned char val) {
    z80_outp(port, val);
}

unsigned int k_inp(unsigned int port) {
    return z80_inp(port);
}
Gives:

Code: Select all

zcc +embedded -clib=sdcc_iy blah.c -c
blah.c:14: error 98: conflict with previous declaration of 'k_outp' for attribute 'type' at blah.c:7
from type 'void function ( unsigned-int fixed, unsigned-int fixed) fixed'
  to type 'void function ( unsigned-int fixed, unsigned-char fixed) fixed'
So maybe check that that they're matching?
User avatar
dom
Well known member
Posts: 2269
Joined: Sun Jul 15, 2007 10:01 pm

Re: Newb questions for developing OS for new target

Post by dom »

cyrusbuilt wrote: Wed Jan 15, 2025 3:55 pm Thank you! That was most helpful.

I now have the following in Common/io.c

...


Now when I compile I get the following:

Code: Select all

zcc +embedded -vn -c -SO3 --max-allocs-per-node200000 -compiler sdcc -clib=sdcc_iy -pragma-include:zpragma.inc -o Common/io.o Common/io.c
zcc +embedded -vn -clib=sdcc_iy -pragma-include:zpragma.inc -startup=1 Kernel/kernel.o Common/io.o -o kernel -create-app
stdio/z80/asm_puts_unlocked.asm:42: error: undefined symbol: _stdout
  ^---- (_stdout)
stdio/z80/asm_vprintf_unlocked.asm:48: error: undefined symbol: _stdout
  ^---- (_stdout)
make: *** [kernel.bin] Error 1
So I'm assuming the target doesn't define _stdout and I need to define it myself. I'm not exactly sure how to go about doing that. And if _stdout isn't defined, I'm assuming I'll also need to define _stdin somehow as well.
Okay, I can see things have got a little confusing. You're using a newlib compile line (+embedded -clib=sdcc_iy) but have implemented the fputc_cons_native/fgetc_cons from classic.

Switch over to classic compile line (+z80 -clib=classic -compiler=sdcc) and things should look a bit better. I've not figured out all the right bits of your project, but my single file version of what you're doing:

Code: Select all

// Unknown
#define OP_IO_WR_SER_TX 1
#define OP_IO_RD_SYSFLG 2


#include <sys/compiler.h>
#include <stdio.h>
#include <stdlib.h>     //outp/inp

// io.h
void k_outp(unsigned int port, unsigned int val);
unsigned int k_inp(unsigned int port);

__sfr __at 0x00 EXEC_PORT;
__sfr __at 0x01 STORE_OPC_PORT;
__sfr __at 0x01 SER_RX_PORT;

// io.c
#define LF 0x0a
#define CR 0x0d
#define SER_BUF_EMPTY_BIT 0x04

void k_outp(unsigned int port, unsigned int val) {
    outp(port, val);
}

unsigned int k_inp(unsigned int port) {
    return inp(port);
}

int fputc_cons_native(char c)  {
    if (c == LF) {
        STORE_OPC_PORT = OP_IO_WR_SER_TX;
        EXEC_PORT = (char)(CR);
    }

    STORE_OPC_PORT = OP_IO_WR_SER_TX;
    EXEC_PORT = c;
    return c;
}

int fgetc_cons(void)  {
    static char sysFlags;
    do {
        STORE_OPC_PORT = OP_IO_RD_SYSFLG;
        sysFlags = EXEC_PORT;
    } while (!(sysFlags & SER_BUF_EMPTY_BIT));
    return SER_RX_PORT;
}

// SDCC exporting fudge to handle the double export of library functions
#ifdef __SDCC
static void fudge_export(void)
{
__asm
        PUBLIC fputc_cons_native
        PUBLIC _fputc_cons_native
        defc fputc_cons_native = _fputc_cons_native
        defc fgetc_cons = _fgetc_cons
__endasm;
}
#endif

// main.c

int main(void)
{
    printf("Hello world\n");
    fgetc_cons();
    return 0;
}

// zcc +z80 -clib=classic io.c
// zcc +z80 -clib=classic -compiler=sdcc io.c
Compiles, links and produces a binary. Note that z80_inp and z80_outp changed to inp/outp, and __naked doesn't apply to a C function.

[Note, I've updated this post to support sdcc after I originally posted it]
cyrusbuilt
New member
Posts: 4
Joined: Mon Jan 13, 2025 8:15 pm

Re: Newb questions for developing OS for new target

Post by cyrusbuilt »

Thank you! That definitely fixed a bunch of stuff. I was doing pretty good until I actually had a need to call fgets() which now causes the following error on the last build step:

stdio/fgetc.c::fgetc::0::0:20: error: undefined symbol: fgetc_cons
^---- fgetc_cons

Admittedly, I haven't done a ton of programming in pure C, so I have a feeling this is just a problem with my make file or the way I'm calling the compiler or maybe the order in which things are being built. Rather than copying and pasting code here, here is a link to the repo instead: https://github.com/cyrusbuilt/CyOS

I'm not sure how it's undefined since I've defined it in io.c
User avatar
dom
Well known member
Posts: 2269
Joined: Sun Jul 15, 2007 10:01 pm

Re: Newb questions for developing OS for new target

Post by dom »

Sorry that's my fault, try this fudge block:

Code: Select all

// SDCC exporting fudge to handle the double export of library functions
#ifdef __SDCC
static void fudge_export(void)
{
__asm
        PUBLIC fputc_cons_native
        PUBLIC _fputc_cons_native
        PUBLIC fgetc_cons
        PUBLIC _fgetc_cons
        defc fputc_cons_native = _fputc_cons_native
        defc fgetc_cons = _fgetc_cons
__endasm;
}
#endif
The fgetc_cons symbol wasn't being exported.
Post Reply