Add new target

Message ID 1754337816158.7.107050@webmail-backend-production-957f95bc8-4zxxx
State New
Headers
Series Add new target |

Commit Message

micael@hildenborg.com Aug. 4, 2025, 8:03 p.m. UTC
  This adds vendor "atari" for target m68k-elf, resulting in a new possible target: "m68k-atari-elf".

Sorry for spamming the mailinglist earlier. I'm new to this kind of work and didn't read all instructions before previous posts...
  

Comments

Jeff Johnston Aug. 7, 2025, 6:08 p.m. UTC | #1
Patch applied.

Thanks,

-- Jeff J.

On Mon, Aug 4, 2025 at 4:04 PM <micael@hildenborg.com> wrote:

>
> This adds vendor "atari" for target m68k-elf, resulting in a new possible
> target: "m68k-atari-elf".
>
> Sorry for spamming the mailinglist earlier. I'm new to this kind of work
> and didn't read all instructions before previous posts...
>
>
  

Patch

From 7b685033ded7f003b8d45c52da61e8af852726bd Mon Sep 17 00:00:00 2001
From: Mikael Hildenborg <micael@hildenborg.com>
Date: Mon, 4 Aug 2025 21:50:29 +0200
Subject: [PATCH 1/1] Added target m68k-atari-elf supporting Atari 16/32bit TOS
 systems.

---
 COPYING.LIBGLOSS                         |   4 +
 COPYING.NEWLIB                           |   4 +
 libgloss/configure                       |  24 +-
 libgloss/configure.ac                    |   5 +
 libgloss/m68k/Makefile.in                |  77 +++++-
 libgloss/m68k/atari/README               |  35 +++
 libgloss/m68k/atari/atari-chdir.c        |  49 ++++
 libgloss/m68k/atari/atari-chown.c        |  20 ++
 libgloss/m68k/atari/atari-close.c        |  23 ++
 libgloss/m68k/atari/atari-crt0.S         |  74 ++++++
 libgloss/m68k/atari/atari-crti.S         |  90 +++++++
 libgloss/m68k/atari/atari-crtn.S         |  24 ++
 libgloss/m68k/atari/atari-environ.c      |   8 +
 libgloss/m68k/atari/atari-execve.c       |  16 ++
 libgloss/m68k/atari/atari-fork.c         |  19 ++
 libgloss/m68k/atari/atari-fstat.c        |  51 ++++
 libgloss/m68k/atari/atari-gem_basepage.h |  58 +++++
 libgloss/m68k/atari/atari-gem_errno.c    |  95 +++++++
 libgloss/m68k/atari/atari-gem_errno.h    |  57 +++++
 libgloss/m68k/atari/atari-getcwd.c       |  32 +++
 libgloss/m68k/atari/atari-getentropy.c   |  52 ++++
 libgloss/m68k/atari/atari-getpid.c       |  11 +
 libgloss/m68k/atari/atari-gettod.c       | 135 ++++++++++
 libgloss/m68k/atari/atari-isatty.c       |  12 +
 libgloss/m68k/atari/atari-kill.c         |  16 ++
 libgloss/m68k/atari/atari-link.c         |  19 ++
 libgloss/m68k/atari/atari-lseek.c        |  43 ++++
 libgloss/m68k/atari/atari-mkdir.c        |  21 ++
 libgloss/m68k/atari/atari-open.c         |  78 ++++++
 libgloss/m68k/atari/atari-read.c         |  44 ++++
 libgloss/m68k/atari/atari-readlink.c     |  20 ++
 libgloss/m68k/atari/atari-rename.c       |  20 ++
 libgloss/m68k/atari/atari-rmdir.c        |  20 ++
 libgloss/m68k/atari/atari-sbrk.c         |  24 ++
 libgloss/m68k/atari/atari-stat.c         |  29 +++
 libgloss/m68k/atari/atari-symlink.c      |  19 ++
 libgloss/m68k/atari/atari-times.c        |  37 +++
 libgloss/m68k/atari/atari-tos.ld         | 160 ++++++++++++
 libgloss/m68k/atari/atari-tos.specs      |  10 +
 libgloss/m68k/atari/atari-traps.c        | 311 +++++++++++++++++++++++
 libgloss/m68k/atari/atari-traps.h        |  84 ++++++
 libgloss/m68k/atari/atari-unlink.c       |  19 ++
 libgloss/m68k/atari/atari-wait.c         |  16 ++
 libgloss/m68k/atari/atari-write.c        |  87 +++++++
 newlib/configure.host                    |   5 +-
 45 files changed, 2043 insertions(+), 14 deletions(-)
 create mode 100644 libgloss/m68k/atari/README
 create mode 100644 libgloss/m68k/atari/atari-chdir.c
 create mode 100644 libgloss/m68k/atari/atari-chown.c
 create mode 100644 libgloss/m68k/atari/atari-close.c
 create mode 100644 libgloss/m68k/atari/atari-crt0.S
 create mode 100644 libgloss/m68k/atari/atari-crti.S
 create mode 100644 libgloss/m68k/atari/atari-crtn.S
 create mode 100644 libgloss/m68k/atari/atari-environ.c
 create mode 100644 libgloss/m68k/atari/atari-execve.c
 create mode 100644 libgloss/m68k/atari/atari-fork.c
 create mode 100644 libgloss/m68k/atari/atari-fstat.c
 create mode 100644 libgloss/m68k/atari/atari-gem_basepage.h
 create mode 100644 libgloss/m68k/atari/atari-gem_errno.c
 create mode 100644 libgloss/m68k/atari/atari-gem_errno.h
 create mode 100644 libgloss/m68k/atari/atari-getcwd.c
 create mode 100644 libgloss/m68k/atari/atari-getentropy.c
 create mode 100644 libgloss/m68k/atari/atari-getpid.c
 create mode 100644 libgloss/m68k/atari/atari-gettod.c
 create mode 100644 libgloss/m68k/atari/atari-isatty.c
 create mode 100644 libgloss/m68k/atari/atari-kill.c
 create mode 100644 libgloss/m68k/atari/atari-link.c
 create mode 100644 libgloss/m68k/atari/atari-lseek.c
 create mode 100644 libgloss/m68k/atari/atari-mkdir.c
 create mode 100644 libgloss/m68k/atari/atari-open.c
 create mode 100644 libgloss/m68k/atari/atari-read.c
 create mode 100644 libgloss/m68k/atari/atari-readlink.c
 create mode 100644 libgloss/m68k/atari/atari-rename.c
 create mode 100644 libgloss/m68k/atari/atari-rmdir.c
 create mode 100644 libgloss/m68k/atari/atari-sbrk.c
 create mode 100644 libgloss/m68k/atari/atari-stat.c
 create mode 100644 libgloss/m68k/atari/atari-symlink.c
 create mode 100644 libgloss/m68k/atari/atari-times.c
 create mode 100644 libgloss/m68k/atari/atari-tos.ld
 create mode 100644 libgloss/m68k/atari/atari-tos.specs
 create mode 100644 libgloss/m68k/atari/atari-traps.c
 create mode 100644 libgloss/m68k/atari/atari-traps.h
 create mode 100644 libgloss/m68k/atari/atari-unlink.c
 create mode 100644 libgloss/m68k/atari/atari-wait.c
 create mode 100644 libgloss/m68k/atari/atari-write.c

diff --git a/COPYING.LIBGLOSS b/COPYING.LIBGLOSS
index 7eed3ab29..7dbcbfec1 100644
--- a/COPYING.LIBGLOSS
+++ b/COPYING.LIBGLOSS
@@ -497,3 +497,7 @@  and need not follow the licensing terms described here, provided that
 the new terms are clearly indicated on the first page of each file where
 they apply.
 
+(26) - Atari 16/32 bit TOS (m68k-atari-elf target)
+
+Copyright (C) 2025 Mikael Hildenborg
+SPDX-License-Identifier: BSD-2-Clause
diff --git a/COPYING.NEWLIB b/COPYING.NEWLIB
index 9a2680d09..2bf6f0bed 100644
--- a/COPYING.NEWLIB
+++ b/COPYING.NEWLIB
@@ -1570,3 +1570,7 @@  Software.
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+(58) - Atari 16/32 bit TOS (m68k-atari-elf target)
+
+Copyright (C) 2025 Mikael Hildenborg
+SPDX-License-Identifier: BSD-2-Clause
diff --git a/libgloss/configure b/libgloss/configure
index 3eb4c2740..369780c64 100755
--- a/libgloss/configure
+++ b/libgloss/configure
@@ -2857,6 +2857,11 @@  case "${target}" in
 	ac_config_files="$ac_config_files m68k/Makefile"
 
 	subdirs="$subdirs m68k"
+	case "${target}" in
+		m68k-atari-*)
+			config_libnosys=false
+		;;
+	esac
 	;;
   hppa*-*-pro*)
 	ac_config_files="$ac_config_files pa/Makefile"
@@ -5461,16 +5466,18 @@  case "${target}" in
     MIPS_SCRIPT_LIST="dve idt jmr3904app jmr3904dram jmr3904dram-java jmr3904app-java sde32 sde64 mti32 mti64 mti64_n32 mti64_64"
     MIPS_BSP_LIST="libdve.a libidt.a libjmr3904.a"
     ;;
+  mipsisa32-*-* | mipsisa32el-*-* | \
+  mipsisa32r2-*-* | mipsisa32r2el-*-* | \
+  mipsisa64*-*-*)
+    MIPS_CRT0="crt0_cfe.o crt0_cygmon.o crt0.o"
+    MIPS_SCRIPT_LIST="idt32 idt64 cfe"
+    MIPS_BSP_LIST="libcygmon.a libidt.a libcfe.a"
+    ;;
   mips*-lsi*-*)
     MIPS_PART_SPECIFIC_OBJ="entry.o"
     MIPS_SCRIPT_LIST="lsi"
     MIPS_BSP_LIST=liblsi.a
     ;;
-  mips*-mti*-* | mips*-img*-*)
-    MIPS_CRT0="crt0.o"
-    MIPS_SCRIPT_LIST="idt nullmon mti32 mti64_n32 mti64_64 uhi32 uhi64_64 uhi64_n32 malta32-yamon bootcode"
-    MIPS_BSP_LIST="libidt.a libnullmon.a libuhi.a libyamon.a libhal.a libcm3_impl.a"
-    ;;
   mips64vr5*-*-*)
     MIPS_PART_SPECIFIC_OBJ="vr5xxx.o cma101.o"
     MIPS_PART_SPECIFIC_DEFINES=-DR5000
@@ -5482,13 +5489,6 @@  case "${target}" in
     MIPS_SCRIPT_LIST="ddb ddb-kseg0 nullmon"
     MIPS_BSP_LIST="libpmon.a libnullmon.a"
     ;;
-  mipsisa32-*-* | mipsisa32el-*-* | \
-  mipsisa32r2-*-* | mipsisa32r2el-*-* | \
-  mipsisa64*-*-*)
-    MIPS_CRT0="crt0_cfe.o crt0_cygmon.o crt0.o"
-    MIPS_SCRIPT_LIST="idt32 idt64 cfe"
-    MIPS_BSP_LIST="libcygmon.a libidt.a libcfe.a"
-    ;;
   mips*)
     MIPS_CRT0="crt0_cfe.o crt0.o"
     MIPS_PART_SPECIFIC_OBJ="vr4300.o cma101.o"
diff --git a/libgloss/configure.ac b/libgloss/configure.ac
index 628f173d6..0ab0cc248 100644
--- a/libgloss/configure.ac
+++ b/libgloss/configure.ac
@@ -79,6 +79,11 @@  case "${target}" in
   fido-*-* | m68*-*-*)
 	AC_CONFIG_FILES([m68k/Makefile])
 	subdirs="$subdirs m68k"
+	case "${target}" in
+		m68k-atari-*)
+			config_libnosys=false
+		;;
+	esac
 	;;
   hppa*-*-pro*)
 	AC_CONFIG_FILES([pa/Makefile])
diff --git a/libgloss/m68k/Makefile.in b/libgloss/m68k/Makefile.in
index 7f67426a6..b6d1868bc 100644
--- a/libgloss/m68k/Makefile.in
+++ b/libgloss/m68k/Makefile.in
@@ -31,6 +31,7 @@  program_transform_name = @program_transform_name@
 bindir = @bindir@
 libdir = @libdir@
 tooldir = $(exec_prefix)/$(target_alias)
+specsdir = $(libdir)/gcc/$(target_alias)
 
 # Multilib support variables.
 # TOP is used instead of MULTI{BUILD,SRC}TOP.
@@ -71,6 +72,14 @@  OBJS =  close.o fstat.o getpid.o isatty.o kill.o \
 # What targets to build for.  This is a list of {m68k,cf}
 TARGET = @M68K_TARGET@
 
+# Check if vendor is atari and if so, adjust the target
+target_vendor = @target_vendor@
+ifdef target_vendor
+	ifeq ($(target_vendor), atari)
+		TARGET := $(TARGET)_$(target_vendor)
+	endif
+endif
+
 # Here is all of the simulator stuff
 SIM_SCRIPTS	= sim.ld
 SIM_LDFLAGS	= -Tsim.ld
@@ -197,6 +206,29 @@  UNHOSTED_OBJS=	unhosted-close.o unhosted-fstat.o \
 HOSTED_BSP=	libhosted.a
 HOSTED_OBJS=	hosted-gdb.o hosted-exit.o $(patsubst un%,%,$(UNHOSTED_OBJS))
 
+#
+# here's all the atari stuff
+#
+
+ATARI_SRC_LIBGLOSS := atari-environ.c atari-execve.c atari-link.c atari-times.c \
+	atari-wait.c atari-traps.c atari-gem_errno.c atari-close.c atari-fstat.c \
+	atari-getpid.c atari-isatty.c atari-kill.c atari-lseek.c atari-open.c atari-read.c \
+	atari-sbrk.c atari-stat.c atari-unlink.c atari-write.c \
+	atari-chown.c atari-fork.c atari-getentropy.c atari-gettod.c atari-readlink.c \
+	atari-symlink.c atari-chdir.c atari-getcwd.c atari-mkdir.c atari-rmdir.c \
+	atari-rename.c
+
+ATARI_OBJS_LIBGLOSS := $(foreach source,$(ATARI_SRC_LIBGLOSS),$(basename $(source)).o)
+
+ATARI_CRTI := atari-crti.o
+ATARI_CRTN := atari-crtn.o
+ATARI_CRT0 := atari-crt0.o
+ATARI_NEWLIB := libatari-tos.a
+ATARI_LINK := atari-tos.ld
+ATARI_SPECS := atari-tos.specs
+
+ATARI_ASFLAGS := -Xassembler --register-prefix-optional
+
 # Host specific makefile fragment comes in here.
 @host_makefile_frag@
 
@@ -206,9 +238,19 @@  all :: $(TARGET:%=all_%)
 # here's where we build the board support packages for each target
 #
 
+.PHONY: all_m68k_atari
+all_m68k_atari :: $(ATARI_CRTI) $(ATARI_CRTN) $(ATARI_CRT0) $(ATARI_NEWLIB)
+
+.PHONY: all_cf_atari
+all_cf_atari :: all_m68k_atari
+
+.PHONY: all_fido_atari
+all_fido_atari :: all_m68k_atari
+
 .PHONY: all_m68k
 all_m68k :: ${SIM_CRT0} ${SIM_BSP} ${CRT0} ${BCC_BSP} ${IDP_BSP} \
 	${IDPGDB_BSP} ${MVME135_BSP} ${MVME162_BSP}
+
 .PHONY: all_cf
 all_cf :: ${CF_CRT0S} ${CF_BSP} ${CF_SCRIPTS} ${HOSTED_BSP} ${UNHOSTED_BSP}
 
@@ -240,6 +282,23 @@  ${MVME162_BSP}: $(OBJS) ${MVME162_OBJS}
 	${AR} ${ARFLAGS} $@ $(OBJS) ${MVME162_OBJS}
 	${RANLIB} $@
 
+# build atari library
+$(ATARI_CRT0): atari/atari-crt0.S
+	$(CC) $(CFLAGS_FOR_TARGET) $(CFLAGS) $(ATARI_ASFLAGS) -c $< -o $@
+
+$(ATARI_CRTI): atari/atari-crti.S
+	$(CC) $(CFLAGS_FOR_TARGET) $(CFLAGS) $(ATARI_ASFLAGS) -c $< -o $@
+
+$(ATARI_CRTN): atari/atari-crtn.S
+	$(CC) $(CFLAGS_FOR_TARGET) $(CFLAGS) $(ATARI_ASFLAGS) -c $< -o $@
+
+%.o: atari/%.c
+	$(CC) $(CFLAGS_FOR_TARGET) $(CFLAGS) $(INCLUDES) -c $< -o $@
+
+$(ATARI_NEWLIB): $(ATARI_OBJS_LIBGLOSS)
+	$(AR) $(ARFLAGS) $@ $^
+	$(RANLIB) $@
+
 # build hosted library
 $(HOSTED_OBJS): hosted%.o: io%.c io.h
 	$(CC) $(CFLAGS_FOR_TARGET) $(CFLAGS) -DHOSTED=1 $(INCLUDES) -c $< -o $@
@@ -409,6 +468,22 @@  unlink.o: $(srcdir)/../unlink.c
 install:: $(TARGET:%=install_%)
 
 
+.PHONY:install_m68k_atari
+install_m68k_atari::
+	${mkinstalldirs} ${DESTDIR}${tooldir}/lib${MULTISUBDIR}
+	$(INSTALL_PROGRAM) $(ATARI_CRTI) $(DESTDIR)$(tooldir)/lib${MULTISUBDIR}/crti.o
+	$(INSTALL_PROGRAM) $(ATARI_CRTN) $(DESTDIR)$(tooldir)/lib${MULTISUBDIR}/crtn.o
+	$(INSTALL_PROGRAM) $(ATARI_CRT0) $(DESTDIR)$(tooldir)/lib${MULTISUBDIR}/crt0.o
+	$(INSTALL_PROGRAM) $(ATARI_NEWLIB) $(DESTDIR)$(tooldir)/lib${MULTISUBDIR}/$(ATARI_NEWLIB)
+	$(INSTALL_DATA) ${srcdir}/atari/$(ATARI_LINK) $(DESTDIR)$(tooldir)/lib${MULTISUBDIR}/$(ATARI_LINK)
+	$(INSTALL_DATA) ${srcdir}/atari/$(ATARI_SPECS) $(DESTDIR)$(specsdir)/specs
+
+.PHONY:install_cf_atari
+install_cf_atari:: install_m68k_atari
+
+.PHONY:install_fido_atari
+install_fido_atari:: install_m68k_atari
+
 .PHONY:install_m68k
 install_m68k::
 	${mkinstalldirs} ${DESTDIR}${tooldir}/lib${MULTISUBDIR}
@@ -473,7 +548,7 @@  install_io::
 @target_makefile_frag@
 
 clean mostlyclean:
-	rm -f a.out core *.i *~ *.a *.o *-test *.srec *.dis *.x *.map
+	rm -f a.out core *.i *~ *.a *.o *-test *.srec *.dis *.x *.map *.ld
 
 distclean maintainer-clean realclean: clean
 	rm -f Makefile
diff --git a/libgloss/m68k/atari/README b/libgloss/m68k/atari/README
new file mode 100644
index 000000000..24d2a6ea6
--- /dev/null
+++ b/libgloss/m68k/atari/README
@@ -0,0 +1,35 @@ 
+Copyright (C) 2025 Mikael Hildenborg
+SPDX-License-Identifier: BSD-2-Clause
+
+Atari 16/32 bit TOS (not MINT) target.
+Target name: m68k-atari-elf.
+
+Stack size:
+The default stack size is set at 2000 bytes.
+To change stack size, add the following line to your C/C++ code:
+unsigned int _STACK_SIZE = *WANTED_STACK_SIZE*;
+Where "*WANTED_STACK_SIZE*" is the size in bytes you want to allocate for stack space.
+Do not use odd size!
+
+Heap size:
+The default heap size is to use all available memory.
+If you want to leave memory for other programs to use, then add the following line to your C/C++ code:
+unsigned int _HEAP_SIZE = *WANTED_HEAP_SIZE*;
+Where "*WANTED_HEAP_SIZE*" is the size in bytes you want to allocate for heap space.
+Do not use odd size!
+
+Program base page:
+A pointer to the program base page is stored at: _BasePage
+
+ELF output:
+The executable elf output have four interesting segments:
+".text", ".data", ".bss" and ".prgheader" as described in "atari-tos.ld".
+All relevant code and data have been properly sorted into those segments, and the ".prgheader"
+contains a valid atari prg header pointing to relevant segments.
+Relocation data from the link switch "--emit-relocs" (defaultted by "atari-tos.specs") is
+included in the elf output so additional fixup information can be added to the prg file.
+Addresses in elf and prg files are the same and thus compatible for interchangeable symbol lookup.
+
+More information:
+Can be found at: https://github.com/hildenborg/m68k-atari-dev
+Where build scripts for complete toolchain, executables for converting elf to prg, and a remote debug server with full symbolic debugging are available.
diff --git a/libgloss/m68k/atari/atari-chdir.c b/libgloss/m68k/atari/atari-chdir.c
new file mode 100644
index 000000000..edb7a70d8
--- /dev/null
+++ b/libgloss/m68k/atari/atari-chdir.c
@@ -0,0 +1,49 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+int chdir(const char *path)
+{
+	int err = GEM_E_OK;
+	if (path != 0)
+	{
+		if (path[1] == ':')
+		{
+			unsigned int drives = 0;
+			unsigned short drive = 0;
+			if (path[0] >= 'A' && path[0] <= 'Z')
+			{
+				drive = (unsigned short)(path[0] - 'A');
+			}
+			else if (path[0] >= 'a' && path[0] <= 'z')
+			{
+				drive = (unsigned short)(path[0] - 'a');
+			}
+			drives = trap1_e(drive);
+			if ((drives & (1 << drive)) == 0)
+			{
+				err = GEM_EDRIVE;
+			}
+			path += 2;
+		}
+		if (err == GEM_E_OK)
+		{
+			err = trap1_3b(path);
+		}
+	}
+	else
+	{
+		err = GEM_EPTHNF;
+	}
+	if (err < 0)
+	{
+		gem_error_to_errno(err);
+		return -1;
+	}
+	return 0;
+}
\ No newline at end of file
diff --git a/libgloss/m68k/atari/atari-chown.c b/libgloss/m68k/atari/atari-chown.c
new file mode 100644
index 000000000..62f830002
--- /dev/null
+++ b/libgloss/m68k/atari/atari-chown.c
@@ -0,0 +1,20 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "config.h"
+#include <_ansi.h>
+#include <_syslist.h>
+#include <sys/types.h>
+#include <errno.h>
+#include "atari-gem_errno.h"
+#include "libnosys/warning.h"
+
+int chown(const char *path, uid_t owner, gid_t group)
+{
+    errno = ENOSYS;
+    return -1;
+}
+
+stub_warning(chown)
diff --git a/libgloss/m68k/atari/atari-close.c b/libgloss/m68k/atari/atari-close.c
new file mode 100644
index 000000000..a52049c1d
--- /dev/null
+++ b/libgloss/m68k/atari/atari-close.c
@@ -0,0 +1,23 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+int close(int fd)
+{
+	int err = GEM_EIHNDL;
+	if (fd >= 0)
+	{
+		err = trap1_3e((unsigned short)fd);
+	}
+	if (err <  0)
+	{
+		gem_error_to_errno(err);
+		return -1;
+	}
+	return 0;
+}
diff --git a/libgloss/m68k/atari/atari-crt0.S b/libgloss/m68k/atari/atari-crt0.S
new file mode 100644
index 000000000..6d1bbd1d9
--- /dev/null
+++ b/libgloss/m68k/atari/atari-crt0.S
@@ -0,0 +1,74 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+	.global exit
+
+	.section	".init"
+	.global	__call_main_with_argc_argv
+	.type	__call_main_with_argc_argv,#function
+__call_main_with_argc_argv:
+
+/*
+	Need to implement "The Atari Extended Argument Specification".
+	Also, there is no guarantee that the command line is zero terminated.
+*/
+    | this occurs after crtbegin.o have done all global constructors etc.
+
+	move.l	a7, a6
+	moveq	#0, d0
+	moveq	#0, d2
+	movea.l	_BasePage, a0
+	lea		128(a0), a0		| add offset to the cmdline
+	lea		_cmdline, a1
+	move.b	(a0)+, d2		| d2 contains number of bytes in command line (max 127)
+	beq.s	3f
+	clr.b	(a1, d2.w)		| end our decoded args with a zero.
+	bra.s	2f
+1:
+	move.b	(a0, d2.w), d1
+	cmp.b	#' ', d1
+	bne.s	4f
+	moveq	#0, d1
+	lea		1(a1, d2.w), a2
+	tst.b	(a2)
+	beq.s	4f
+	move.l	a2, -(a7)
+	addq.w	#1, d0
+4:
+	move.b	d1, (a1, d2.w)
+2:
+	subq.w	#1, d2
+	bcc.s	1b
+	move.l	a1, -(a7)
+	addq.w	#1, d0
+3:
+	pea		_procname		| first argument is always the proc name. That we do not know...
+	addq.w	#1, d0
+	move.l	a7, a5
+	move.l	a6, -(a7)		| To know where to move it back again.
+
+	move.l	a5, -(a7)		| argv
+	move.l	d0, -(a7)		| argc
+	jsr		main
+	move.l	8(a7), a7		| move it back.
+	move.l	d0, -(a7)
+	jsr		exit			| not expected to return
+	illegal
+
+	.global _exit
+	.type	_exit,#function
+_exit:
+	move.l 4(a7), d0
+	move.w	d0, _program_return_code
+    | crtend.o follows here with global destructors etc.
+
+	.data
+_procname:
+	.asciz	"yourapp.lol"
+	.even
+
+	.bss
+	.lcomm	_cmdline, 128
+	.even
diff --git a/libgloss/m68k/atari/atari-crti.S b/libgloss/m68k/atari/atari-crti.S
new file mode 100644
index 000000000..5e4c13b0e
--- /dev/null
+++ b/libgloss/m68k/atari/atari-crti.S
@@ -0,0 +1,90 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#ifdef _HAVE_INITFINI_ARRAY
+#define _init	__libc_init_array
+#define _fini	__libc_fini_array
+#endif
+
+	.equ	DEFAULT_STACK, 2000
+
+	.global _BasePage
+	.weak 	_STACK_SIZE
+	.weak 	_HEAP_SIZE
+	.global _HeapPtr
+	.global _HeapBottom
+	.global _HeapTop
+	.global _atari_4ba_at_prg_start
+	.global __BSS_SEGMENT_END
+
+	.section	".init"
+	.global	_init
+	.type	_init,#function
+_init:
+	move.l	4(a7),a0
+	move.l	a0, _BasePage
+
+	| Init stack
+	lea		__BSS_SEGMENT_END, a2
+	lea		_STACK_SIZE, a1
+	cmpa.w	#0, a1
+	jeq		default_stack_size
+	add.l	(a1), a2
+	jra		stack_size_selected
+default_stack_size:
+	lea		DEFAULT_STACK(a2), a2
+stack_size_selected:
+	move.l	a2, a7
+
+	| Init heap
+	move.l	a7, d0
+	move.l	d0, _HeapBottom
+	move.l	d0, _HeapPtr
+	move.l	4(a0), _HeapTop
+
+	lea		_HEAP_SIZE, a1
+	cmpa.w	#0, a1
+	jeq		heap_setup_done
+	add.l	(a1), d0
+	sub.l	(a0), d0			| d0 is now the TPA size
+
+	| Program do not want all memory, so we shrink it.
+	move.l	d0, -(a7)
+	move.l	a0, -(a7)
+	clr.w	-(a7)
+	move.w	#0x4a, -(a7)	| Mshrink()
+	trap	#1
+	lea		12(a7), a7
+	tst.l	d0
+	jpl		heap_setup_done
+    | Error, just quit with d0 as return code.
+	move.w	d0,-(a7)
+	move.w	#0x4c,-(a7)
+	trap	#1
+super_init:
+	| Init stuff that needs supervisor mode set.
+	move.l	0x4ba, _atari_4ba_at_prg_start
+	rts
+heap_setup_done:
+	move.l	#super_init, -(a7)
+	move.w	#0x26, -(a7)
+	trap	#14
+	addq.l	#6, a7
+    | crtbegin.o follows here with global constructors etc. init.
+
+
+	.section	".fini"
+	/*
+		Empty.
+	*/
+
+
+	.bss
+	.lcomm 	_BasePage, 4
+	.lcomm 	_HeapPtr, 4
+	.lcomm 	_HeapBottom, 4
+	.lcomm 	_HeapTop, 4
+	.lcomm	_atari_4ba_at_prg_start, 4
+	.even
\ No newline at end of file
diff --git a/libgloss/m68k/atari/atari-crtn.S b/libgloss/m68k/atari/atari-crtn.S
new file mode 100644
index 000000000..6482a9c9f
--- /dev/null
+++ b/libgloss/m68k/atari/atari-crtn.S
@@ -0,0 +1,24 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+    .global _program_return_code
+
+	.section	".init"
+	/*
+		Empty.
+	*/
+
+
+	.section	".fini"
+	.global	_fini
+	.type	_fini,#function
+_fini:
+    | this occurs after crtend.o have done all global destructors etc.
+	move.w	_program_return_code,-(a7)
+	move.w	#0x4c,-(a7)
+	trap	#1
+
+	.bss
+    .lcomm  _program_return_code, 2
diff --git a/libgloss/m68k/atari/atari-environ.c b/libgloss/m68k/atari/atari-environ.c
new file mode 100644
index 000000000..3f4970733
--- /dev/null
+++ b/libgloss/m68k/atari/atari-environ.c
@@ -0,0 +1,8 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+// Should point to a list of global environment variables.
+char *__env[1] = { 0 };
+char **environ = __env;
\ No newline at end of file
diff --git a/libgloss/m68k/atari/atari-execve.c b/libgloss/m68k/atari/atari-execve.c
new file mode 100644
index 000000000..c7a572154
--- /dev/null
+++ b/libgloss/m68k/atari/atari-execve.c
@@ -0,0 +1,16 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "config.h"
+#include <errno.h>
+#include "libnosys/warning.h"
+
+int execve(char *name, char **argv, char **env)
+{
+    errno = ENOSYS;
+	return -1;
+}
+
+stub_warning(execve)
diff --git a/libgloss/m68k/atari/atari-fork.c b/libgloss/m68k/atari/atari-fork.c
new file mode 100644
index 000000000..e16436fbf
--- /dev/null
+++ b/libgloss/m68k/atari/atari-fork.c
@@ -0,0 +1,19 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "config.h"
+#include <_ansi.h>
+#include <_syslist.h>
+#include <errno.h>
+#include "atari-gem_errno.h"
+#include "libnosys/warning.h"
+
+int fork(void)
+{
+    errno = ENOSYS;
+    return -1;
+}
+
+stub_warning(fork)
diff --git a/libgloss/m68k/atari/atari-fstat.c b/libgloss/m68k/atari/atari-fstat.c
new file mode 100644
index 000000000..a48a00930
--- /dev/null
+++ b/libgloss/m68k/atari/atari-fstat.c
@@ -0,0 +1,51 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <sys/stat.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+#include "atari-gem_basepage.h"
+
+/*
+	From man page:
+	"only the st_uid, st_gid, st_size, and st_mode fields,
+	and only the S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP, S_IROTH, and S_IWOTH file permission bits need be valid."
+*/
+
+int fstat(int fd, struct stat *buf)
+{
+	struct DTA *dta = (struct DTA *)GEM_EIHNDL;
+	// For now, we just support files.
+	if (fd >= 6)
+	{
+		buf->st_mode = S_IRUSR | S_IRGRP | S_IROTH;
+		// files
+		dta = trap1_2f((unsigned int)fd);
+		if ((int)dta >= 0)
+		{
+			// Attribs
+			buf->st_mode |= (dta->d_attrib & FA_DIR) ? S_IFDIR : S_IFREG;
+			if ((dta->d_attrib & FA_READONLY) != 0)
+			{
+				buf->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+			}
+			buf->st_size = dta->d_length;
+			// st_uid, st_gid have no meaning for the st, so we ignore them.
+		}
+	}
+	else
+	{
+		// unsupported, fake something.
+		buf->st_mode = S_IFCHR;
+		buf->st_blksize = 0;
+	}
+	if ((int)dta < 0)
+	{
+		gem_error_to_errno((int)dta);
+		return -1;
+	}
+	return 0;
+}
diff --git a/libgloss/m68k/atari/atari-gem_basepage.h b/libgloss/m68k/atari/atari-gem_basepage.h
new file mode 100644
index 000000000..461c3ce9b
--- /dev/null
+++ b/libgloss/m68k/atari/atari-gem_basepage.h
@@ -0,0 +1,58 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#ifndef GEM_BASEPAGE_H
+#define GEM_BASEPAGE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+	This is the process base page data, containing information about the executable.
+*/
+
+#define FA_READONLY	0x01	// Read-only flag
+#define FA_HIDDEN 0x02		// Hidden file flag
+#define FA_SYSTEM 0x04		// System file flag
+#define FA_VOLUME 0x08		// Volume label flag
+#define FA_DIR 0x10			// Subdirectory
+#define FA_ARCHIVE 0x20		// Archive flag
+
+struct DTA
+{
+	unsigned char d_reserved[21];
+	unsigned char d_attrib;
+	unsigned short d_time;
+	unsigned short d_date;
+	unsigned int d_length;
+	char d_fname[14];
+};
+
+struct BasePage
+{
+	unsigned char* p_lowtpa;	// This LONG contains a pointer to the Transient Program Area (TPA).
+	unsigned char* p_hitpa;		// This LONG contains a pointer to the top of the TPA + 1.
+	unsigned char* p_tbase;		// This LONG contains a pointer to the base of the text segment
+	unsigned int p_tlen;		// This LONG contains the length of the text segment.
+	unsigned char* p_dbase;		// This LONG contains a pointer to the base of the data segment.
+	unsigned int p_dlen;		// This LONG contains the length of the data segment.
+	unsigned char* p_bbase;		// This LONG contains a pointer to the base of the BSS segment.
+	unsigned int p_blen;		// This LONG contains the length of the BSS segment.
+	struct DTA* p_dta;			// This LONG contains a pointer to the processes’ DTA.
+	struct BasePage* p_parent;	// This LONG contains a pointer to the processes’ parent’s basepage.
+	unsigned int p_reserved;	// This LONG is currently unused and is reserved.
+	char* p_env;				// This LONG contains a pointer to the processes’ environment string.
+	unsigned char p_undef[80];	// This area contains 80 unused, reserved bytes.
+	char p_cmdlin[128];			// This area contains a copy of the 128 byte command line string.
+};
+
+extern struct BasePage* _BasePage;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // GEM_BASEPAGE_H
diff --git a/libgloss/m68k/atari/atari-gem_errno.c b/libgloss/m68k/atari/atari-gem_errno.c
new file mode 100644
index 000000000..641a0b212
--- /dev/null
+++ b/libgloss/m68k/atari/atari-gem_errno.c
@@ -0,0 +1,95 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <errno.h>
+#include "atari-gem_errno.h"
+#include <_ansi.h>
+
+/*
+	The conversion between gem errors and errno is not a one to one match,
+	so the goal has been to try and report errors to errno in such way that
+	code depending on this will run as expected.
+	This remains to be proven correct.
+*/
+void gem_error_to_errno(int gem_error)
+{
+	errno = ENOSYS; // Use this as default error code.
+	switch (gem_error)
+	{
+	case GEM_EDRVNR:
+	case GEM_EMEDIA:
+	case GEM_EPAPER:
+		errno = EIO;
+		break;
+	case GEM_EBADRQ:
+		errno = EINVAL;
+		break;
+	case GEM_E_SEEK:
+		errno = ESPIPE;
+		break;
+	case GEM_ESECNF:
+	case GEM_EWRITF:
+	case GEM_EREADF:
+		errno = EIO;
+		break;
+	case GEM_EWRPRO:
+		errno = EROFS;
+		break;
+	case GEM_EUNDEV:
+	case GEM_EDRIVE:
+		errno = ENODEV;
+		break;
+	case GEM_EFILNF:
+	case GEM_EPTHNF:
+	case GEM_EMOUNT:
+		errno = ENOENT;
+		break;
+	case GEM_ENHNDL:
+	case GEM_ENMFIL:
+		errno = EMFILE;
+		break;
+	case GEM_EACCDN:
+		errno = EACCES;
+		break;
+	case GEM_EIHNDL:
+		errno = EBADF;
+		break;
+	case GEM_ENSMEM:
+	case GEM_EGSBF:
+		errno = ENOMEM;
+		break;
+	case GEM_EIMBA:
+		errno = EADDRNOTAVAIL;
+		break;
+	case GEM_ENSAME:
+		errno = EXDEV;
+		break;
+	case GEM_ELOCKED:
+	case GEM_ENSLOCK:
+		errno = ENOLCK;
+		break;
+	case GEM_ENAMETOOLONG:
+		errno = ENAMETOOLONG;
+		break;
+	case GEM_EPLFMT:
+		errno = ENOEXEC;
+		break;
+	case GEM_ELOOP:
+		errno = EMLINK;
+		break;
+	case GEM_E_OK:
+	case GEM_ERROR:
+	case GEM_EUNCMD:
+	case GEM_E_CRC:
+	case GEM_E_CHNG:
+	case GEM_EBADSF:
+	case GEM_EOTHER:
+	case GEM_EINVFN:
+	case GEM_EINTRN:
+	default:
+		errno = -gem_error;
+		break;
+	}
+}
diff --git a/libgloss/m68k/atari/atari-gem_errno.h b/libgloss/m68k/atari/atari-gem_errno.h
new file mode 100644
index 000000000..c61478dba
--- /dev/null
+++ b/libgloss/m68k/atari/atari-gem_errno.h
@@ -0,0 +1,57 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#ifndef GEM_ERRNO_DEFINED
+#define GEM_ERRNO_DEFINED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GEM_E_OK 			0		// No error
+#define GEM_ERROR 			-1		// Generic error
+#define GEM_EDRVNR 			-2		// Drive not ready
+#define GEM_EUNCMD 			-3		// Unknown command
+#define GEM_E_CRC 			-4		// CRC error
+#define GEM_EBADRQ			-5		// Bad request
+#define GEM_E_SEEK			-6		// Seek error
+#define GEM_EMEDIA			-7		// Unknown media
+#define GEM_ESECNF			-8		// Sector not found
+#define GEM_EPAPER			-9		// Out of paper
+#define GEM_EWRITF			-10		// Write fault
+#define GEM_EREADF			-11		// Read fault
+#define GEM_EWRPRO			-12		// Device is write protected
+#define GEM_E_CHNG			-14		// Media change detected
+#define GEM_EUNDEV			-15		// Unknown device
+#define GEM_EBADSF			-16		// Bad sectors on format
+#define GEM_EOTHER			-17		// Insert other disk (request)
+#define GEM_EINVFN			-32		// Invalid function
+#define GEM_EFILNF			-33		// File not found
+#define GEM_EPTHNF			-34		// Path not found
+#define GEM_ENHNDL			-35		// No more handles
+#define GEM_EACCDN			-36		// Access denied
+#define GEM_EIHNDL			-37		// Invalid handle
+#define GEM_ENSMEM			-39		// Insufficient memory
+#define GEM_EIMBA 			-40		// Invalid memory block address
+#define GEM_EDRIVE			-46		// Invalid drive specification
+#define GEM_ENSAME			-48		// Cross device rename
+#define GEM_ENMFIL			-49		// No more files
+#define GEM_ELOCKED			-58		// Record is already locked
+#define GEM_ENSLOCK			-59		// Invalid lock removal request
+#define GEM_ERANGE			-64		// Range error
+#define GEM_ENAMETOOLONG	-64		// Range error
+#define GEM_EINTRN			-65		// Internal error
+#define GEM_EPLFMT			-66		// Invalid program load format
+#define GEM_EGSBF			-67		// Memory block growth failure
+#define GEM_ELOOP			-80		// Too many symbolic links
+#define GEM_EMOUNT			-200	// Mount point crossed (indicator)
+
+void gem_error_to_errno(int gem_error);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // GEM_ERRNO_DEFINED
diff --git a/libgloss/m68k/atari/atari-getcwd.c b/libgloss/m68k/atari/atari-getcwd.c
new file mode 100644
index 000000000..37d778e42
--- /dev/null
+++ b/libgloss/m68k/atari/atari-getcwd.c
@@ -0,0 +1,32 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+char *getcwd(char *buf, size_t size)
+{
+	char *retbuf = buf;
+	if (retbuf == 0)
+	{
+		errno = EIO;
+		return 0;
+	}
+
+	unsigned short drive = trap1_19();
+	int result = trap1_47(retbuf + 2, drive + 1);
+	if (result < 0)
+	{
+		gem_error_to_errno(result);
+		return 0;
+	}
+	retbuf[0] = (char)('A' + drive);
+	retbuf[1] = ':';
+	return retbuf;
+}
diff --git a/libgloss/m68k/atari/atari-getentropy.c b/libgloss/m68k/atari/atari-getentropy.c
new file mode 100644
index 000000000..0b5b461b9
--- /dev/null
+++ b/libgloss/m68k/atari/atari-getentropy.c
@@ -0,0 +1,52 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <_ansi.h>
+#include <_syslist.h>
+#include <errno.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+/*
+    Xorshift pseudorandom number generator.
+    https://en.wikipedia.org/wiki/Xorshift
+    Inited with random seed.
+*/
+
+int getentropy(void *buf, size_t buflen)
+{
+    unsigned int a;
+    unsigned int state[4];
+
+    // Init state
+    a = trap14_11();     // Atari Random func.
+    for (int i = 0; i < 4; ++i)
+    {
+        // xorshift32
+        a ^= (a << 13);
+        a ^= (a >> 17);
+        a ^= (a << 5);
+        state[i] = a;
+    }
+
+    for (size_t i = 0; i < buflen; ++i)
+    {
+        // xorshift128
+        unsigned int t = state[3];
+        unsigned int s = state[0];
+        state[3] = state[2];
+        state[2] = state[1];
+        state[1] = s;
+
+        t ^= t << 11;
+        t ^= t >> 7;
+        state[0] = t ^ s ^ (s >> 19);
+
+        // use lowest byte (could be optimized to adapt to buf len and output whole 32 bits when possible.)
+        ((unsigned char*)buf)[i] = (unsigned char)state[0];
+    }
+
+    return 0;
+}
diff --git a/libgloss/m68k/atari/atari-getpid.c b/libgloss/m68k/atari/atari-getpid.c
new file mode 100644
index 000000000..98e001eb9
--- /dev/null
+++ b/libgloss/m68k/atari/atari-getpid.c
@@ -0,0 +1,11 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <_ansi.h>
+
+int getpid(void)
+{
+	return 1;
+}
diff --git a/libgloss/m68k/atari/atari-gettod.c b/libgloss/m68k/atari/atari-gettod.c
new file mode 100644
index 000000000..5477916a2
--- /dev/null
+++ b/libgloss/m68k/atari/atari-gettod.c
@@ -0,0 +1,135 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <sys/time.h>
+#include <time.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+/*
+	Atari can only handle dates from 1980 to 2107.
+	time_t on m68k is 32bit signed, so that gives us an upper limit of 2038.
+	The code below uses that limitation to simplify the code.
+*/
+
+#define SEC_1900_TO_1980 2524521600
+#define SEC_1900_TO_MAX 0x7fffffff
+#define SECONDS_IN_A_DAY (24 * 60 * 60)
+#define SEC_JAN_AND_FEB ((31 + 29) * SECONDS_IN_A_DAY)	// In a leap year
+#define SECONDS_IN_A_YEAR (365 * SECONDS_IN_A_DAY)
+
+static const short month_to_day[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
+
+int gettimeofday(struct timeval* tv, void* __tz)
+{
+	struct timezone* tz = __tz;
+	if (tz != 0)
+	{
+		// Support for timezone have been removed from linux glibc, so we just fill in a zero timezone.
+		tz->tz_minuteswest = 0;
+		tz->tz_dsttime = 0;
+	}
+	if (tv != 0)
+	{
+		unsigned short date = trap1_2a();
+		unsigned short time = trap1_2c();
+
+		int year = ((date >> 9) & 0x7f);	// from 1980
+		int month = ((date >> 5) & 0xf) - 1;
+		int day = date & 0x1f;
+		int hour = ((time >> 11) & 0x1f);
+		int min = ((time >> 5) & 0x3f);
+		int sec = (time & 0x1f) * 2;
+
+		// Days passed in current year
+		time_t days = day + month_to_day[month];
+		// Add days for every passed year
+		days += year * 365;
+
+		// Add days for every passed leap year
+		int months = year * 12 + month;	// total months
+		months -= 2;	// remove januari and februari
+		if (months > 0)
+		{
+			// We must have passed at least one leap day.
+			int leap_days = (months / (12*4)) + 1;
+			days += leap_days;
+		}
+
+		// Add it all together
+		tv->tv_sec = (((((days * 24) + hour) * 60) + min) * 60) + sec + SEC_1900_TO_1980;
+		tv->tv_usec = 0;
+	}
+	return 0;
+}
+
+int settimeofday(const struct timeval* tv, const struct timezone* tz)
+{
+	// Support for timezone have been removed from linux glibc, so we just ignore it.
+	if (tv != 0)
+	{
+		if (tv->tv_sec < SEC_1900_TO_1980 || tv->tv_sec >= SEC_1900_TO_MAX)
+		{
+			// Outside the ranges we can handle.
+			gem_error_to_errno(GEM_EBADRQ);
+			return -1;
+		}
+
+		time_t seconds = tv->tv_sec - SEC_1900_TO_1980;
+		int year = 0;
+		time_t ysec = SECONDS_IN_A_YEAR;
+		do
+		{
+			ysec = SECONDS_IN_A_YEAR;
+			if ((year % 4) == 0)
+			{
+				ysec += SECONDS_IN_A_DAY;
+			}
+			++year;
+			seconds -= ysec;
+		} while (seconds >= 0);
+		--year;
+		seconds += ysec;
+
+		int minutes = seconds / 60;
+		int hours = minutes / 60;
+		int days = hours / 24;
+		int month = 0;
+		int leap = year % 4;
+		short ld = month_to_day[0];
+		for (int m = 0; m < 12; ++m)
+		{
+			short d = month_to_day[m + 1];
+			if (leap == 0 && m > 0) {d += 1;}
+			if (d > days)
+			{
+				month = m;
+				days -= ld;
+				break;
+			}
+			ld = d;
+		}
+
+		unsigned short date = (unsigned short)(year << 9);
+		date |= (unsigned short)((month + 1) << 5);
+		date |= (unsigned short)days;
+		unsigned short time = (unsigned short)((hours % 24) << 11);
+		time |= (unsigned short)((minutes % 60) << 5);
+		time |= (unsigned short)((seconds % 60) >> 1);
+
+		int err;
+		if ((err = trap1_2b(date)) < 0)
+		{
+			gem_error_to_errno(err);
+			return -1;
+		}
+		if ((err = trap1_2d(time)) < 0)
+		{
+			gem_error_to_errno(err);
+			return -1;
+		}
+	}
+	return 0;
+}
diff --git a/libgloss/m68k/atari/atari-isatty.c b/libgloss/m68k/atari/atari-isatty.c
new file mode 100644
index 000000000..5ab8b9aff
--- /dev/null
+++ b/libgloss/m68k/atari/atari-isatty.c
@@ -0,0 +1,12 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <_ansi.h>
+#include "atari-traps.h"
+
+int isatty(int fd)
+{
+	return fd == GSH_AUX ? 1 : 0;
+}
diff --git a/libgloss/m68k/atari/atari-kill.c b/libgloss/m68k/atari/atari-kill.c
new file mode 100644
index 000000000..cd1d16065
--- /dev/null
+++ b/libgloss/m68k/atari/atari-kill.c
@@ -0,0 +1,16 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+
+/*
+ * kill -- go out via exit...
+ */
+int kill(int pid, int sig)
+{
+	_exit(sig);
+	return 0;
+}
diff --git a/libgloss/m68k/atari/atari-link.c b/libgloss/m68k/atari/atari-link.c
new file mode 100644
index 000000000..e5a1485e5
--- /dev/null
+++ b/libgloss/m68k/atari/atari-link.c
@@ -0,0 +1,19 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "config.h"
+#include <_ansi.h>
+#include <_syslist.h>
+#include <errno.h>
+#include "atari-gem_errno.h"
+#include "libnosys/warning.h"
+
+int link(char *old, char *new)
+{
+    errno = ENOSYS;
+    return -1;
+}
+
+stub_warning(link)
diff --git a/libgloss/m68k/atari/atari-lseek.c b/libgloss/m68k/atari/atari-lseek.c
new file mode 100644
index 000000000..fa703253d
--- /dev/null
+++ b/libgloss/m68k/atari/atari-lseek.c
@@ -0,0 +1,43 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+off_t lseek(int fd, off_t offset, int whence)
+{
+	int newoff = GEM_EIHNDL;
+	if (fd >= 0 && fd <= 2)
+	{
+		// stdin, stdout and stderr just returns OK without doing anything.
+		return 0;
+	}
+	else if (fd >= 3)
+	{
+		// Any file descriptor
+		unsigned short bios_mode = 3; // invalid mode
+		switch (whence)
+		{
+		case SEEK_SET:
+			bios_mode = 0;
+			break;
+		case SEEK_CUR:
+			bios_mode = 1;
+			break;
+		case SEEK_END:
+			bios_mode = 2;
+			break;
+		}
+		newoff = trap1_42((unsigned int)offset, (unsigned short)fd, bios_mode);
+	}
+	if (newoff < 0)
+	{
+		gem_error_to_errno(newoff);
+		return (off_t)-1;
+	}
+	return (off_t)newoff;
+}
diff --git a/libgloss/m68k/atari/atari-mkdir.c b/libgloss/m68k/atari/atari-mkdir.c
new file mode 100644
index 000000000..fdff26b4c
--- /dev/null
+++ b/libgloss/m68k/atari/atari-mkdir.c
@@ -0,0 +1,21 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+int mkdir(const char *pathname, mode_t mode)
+{
+	// Ignore mode, it is not supported by the st.
+	int result = trap1_39(pathname);
+	if (result < 0)
+	{
+		gem_error_to_errno(result);
+		return -1;
+	}
+	return 0;
+}
\ No newline at end of file
diff --git a/libgloss/m68k/atari/atari-open.c b/libgloss/m68k/atari/atari-open.c
new file mode 100644
index 000000000..c61071881
--- /dev/null
+++ b/libgloss/m68k/atari/atari-open.c
@@ -0,0 +1,78 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+// I really don't like having these defines here,
+// fcntl.h where they are, also defines a function for "open",
+// that is different from the one libgloss is supposed to provide...
+#ifndef O_CREAT
+#define O_CREAT 0x0200
+#endif // O_CREAT
+#ifndef O_APPEND
+#define O_APPEND 0x0008
+#endif // O_APPEND
+#ifndef O_EXCL
+#define O_EXCL 0x0800
+#endif // O_EXCL
+#ifndef O_TRUNC
+#define O_TRUNC 0x0400
+#endif // O_TRUNC
+
+// mode is ignored. Those kind of settings is not supported by the st.
+int open(const char *buf, int flags, int mode)
+{
+	int bios_handle = -1;
+	unsigned short bios_mode = (unsigned short)(flags & 0x3); // bits 0-1 the same for st and linux.
+	int create = flags & O_CREAT;
+	int append = flags & O_APPEND; // open doc says that seek end should be done before each write call. We assume that newlib handles that.
+	int excl = flags & O_EXCL;	   // File must be created by this call.
+	int trunc = flags & O_TRUNC;   // File is forced to be created and thus truncated.
+
+	const char *bios_path = buf;
+	if (!trunc)
+	{
+		bios_handle = trap1_3d(bios_path, bios_mode);
+	}
+	if (bios_handle < 0 && (create || trunc))
+	{
+		unsigned short bios_attrib = 0;
+		bios_handle = trap1_3c(bios_path, bios_attrib);
+	}
+	else if (create && excl)
+	{
+		// We explicitly specified that file must be created, and it already existed, so error!
+		gem_error_to_errno(GEM_EACCDN);
+		// Close file.
+		trap1_3e((unsigned short)bios_handle);
+		return -1;
+	}
+
+	if (bios_handle >= 0 && append)
+	{
+		// Seek to end.
+		int new_file_pos = trap1_42(0, (unsigned short)bios_handle, 2);
+		if (new_file_pos < 0)
+		{
+			gem_error_to_errno(new_file_pos);
+			// Close file.
+			trap1_3e((unsigned short)bios_handle);
+			return -1;
+		}
+	}
+
+	if (bios_handle < 0)
+	{
+		gem_error_to_errno(bios_handle);
+		bios_handle = -1;
+	}
+	/*
+		If bios_handle is positive, then the low word is the gemdos handle, and the high word is zero.
+	*/
+	return bios_handle;
+}
diff --git a/libgloss/m68k/atari/atari-read.c b/libgloss/m68k/atari/atari-read.c
new file mode 100644
index 000000000..cdff256e7
--- /dev/null
+++ b/libgloss/m68k/atari/atari-read.c
@@ -0,0 +1,44 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+_READ_WRITE_RETURN_TYPE read(int fd, void *buf, size_t nbytes)
+{
+	int numRead = GEM_EIHNDL;
+	if (fd >= 0)
+	{
+		numRead = trap1_3f((unsigned short)fd, nbytes, buf);
+		if (numRead >= 0 && numRead < nbytes && fd == GSH_CONIN)
+		{
+			/*
+				When reading from console, the EOF character is "\n".
+				Atari tos do not return the EOF character, but posix read do.
+				So we assume that when the trap call have not reached the nbytes limit,
+				then it must have gotten an EOF.
+				So in that case we insert "\n" to the end of the buffer and increase lenth by 1.
+			*/
+			((char*)buf)[numRead] = '\n';
+			++numRead;
+			/*
+				This is not enough however...
+				Atari tos requires a line feed to be "\r\n", and apparently, the "\r" is executed by tos
+				but not returned when reading. This is not a problem for us, but the "\n" as it is an EOF
+				is simply discarded and not executed. So we also need to send a "\n" to stdout to fully comply
+				with standard C behaviour.
+			*/
+			trap1_40(GSH_CONOUT, 1, "\n");
+		}
+	}
+	if (numRead < 0)
+	{
+		gem_error_to_errno(numRead);
+		return -1;
+	}
+	return numRead;
+}
diff --git a/libgloss/m68k/atari/atari-readlink.c b/libgloss/m68k/atari/atari-readlink.c
new file mode 100644
index 000000000..23514a151
--- /dev/null
+++ b/libgloss/m68k/atari/atari-readlink.c
@@ -0,0 +1,20 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "config.h"
+#include <_ansi.h>
+#include <_syslist.h>
+#include <sys/types.h>
+#include <errno.h>
+#include "atari-gem_errno.h"
+#include "libnosys/warning.h"
+
+int readlink(const char *path, char *buf, size_t bufsize)
+{
+    errno = ENOSYS;
+    return -1;
+}
+
+stub_warning(readlink)
diff --git a/libgloss/m68k/atari/atari-rename.c b/libgloss/m68k/atari/atari-rename.c
new file mode 100644
index 000000000..2d7e62da2
--- /dev/null
+++ b/libgloss/m68k/atari/atari-rename.c
@@ -0,0 +1,20 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+int _rename(const char *old_filename, const char *new_filename)
+{
+	int err = trap1_56(old_filename, new_filename);
+	if (err < 0)
+	{
+		gem_error_to_errno(err);
+		return -1;
+	}
+	return 0;
+}
\ No newline at end of file
diff --git a/libgloss/m68k/atari/atari-rmdir.c b/libgloss/m68k/atari/atari-rmdir.c
new file mode 100644
index 000000000..647a5b68f
--- /dev/null
+++ b/libgloss/m68k/atari/atari-rmdir.c
@@ -0,0 +1,20 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+int rmdir(const char *pathname)
+{
+	int result = trap1_3a(pathname);
+	if (result < 0)
+	{
+		gem_error_to_errno(result);
+		return -1;
+	}
+	return 0;
+}
\ No newline at end of file
diff --git a/libgloss/m68k/atari/atari-sbrk.c b/libgloss/m68k/atari/atari-sbrk.c
new file mode 100644
index 000000000..8839cc742
--- /dev/null
+++ b/libgloss/m68k/atari/atari-sbrk.c
@@ -0,0 +1,24 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <errno.h>
+#include <_ansi.h>
+
+extern char *_HeapPtr;
+extern char *_HeapBottom;
+extern char *_HeapTop;
+
+char *sbrk(int nbytes)
+{
+	char *newheap = _HeapPtr + nbytes;
+	if (newheap > _HeapTop)
+	{
+		errno = ENOMEM;
+		return ((char *)-1);
+	}
+	char *retptr = _HeapPtr;
+	_HeapPtr = newheap;
+	return retptr;
+}
diff --git a/libgloss/m68k/atari/atari-stat.c b/libgloss/m68k/atari/atari-stat.c
new file mode 100644
index 000000000..a08273204
--- /dev/null
+++ b/libgloss/m68k/atari/atari-stat.c
@@ -0,0 +1,29 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+
+// I don't like this extern declaration here...
+extern int open(const char *buf, int flags, int mode);
+
+int stat(const char *path, struct stat *buf)
+{
+	int handle = open(path, 0, 0); // read only
+	if (handle >= 0)
+	{
+		int err = fstat(handle, buf);
+		close(handle);
+		handle = err;
+	}
+	if (handle < 0)
+	{
+		gem_error_to_errno(handle);
+		return -1;
+	}
+	return 0;
+}
diff --git a/libgloss/m68k/atari/atari-symlink.c b/libgloss/m68k/atari/atari-symlink.c
new file mode 100644
index 000000000..49bc31886
--- /dev/null
+++ b/libgloss/m68k/atari/atari-symlink.c
@@ -0,0 +1,19 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "config.h"
+#include <_ansi.h>
+#include <_syslist.h>
+#include <errno.h>
+#include "atari-gem_errno.h"
+#include "libnosys/warning.h"
+
+int symlink(const char *path1, const char *path2)
+{
+    errno = ENOSYS;
+    return -1;
+}
+
+stub_warning(symlink)
diff --git a/libgloss/m68k/atari/atari-times.c b/libgloss/m68k/atari/atari-times.c
new file mode 100644
index 000000000..5c825c6ff
--- /dev/null
+++ b/libgloss/m68k/atari/atari-times.c
@@ -0,0 +1,37 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <time.h>
+#include <sys/times.h>
+#include "atari-traps.h"
+#include "atari-gem_errno.h"
+
+// Defined and initialized in atari-crti.S
+extern unsigned int _atari_4ba_at_prg_start;
+
+
+// The memory location read is actually unsigned, so we cast that in the caller func.
+int read200hzMem(void)
+{
+	// 0x4ba is a privileged address only readable in supervisor mode.
+	return *((int*)0x4ba);
+}
+
+clock_t times(struct tms *buf)
+{
+	// Call callback in supervisor mode.
+	unsigned long long int ticks200hz = (unsigned long long int)trap14_26(read200hzMem);
+	unsigned long long int libcticks = (ticks200hz * CLK_TCK) / 200;
+
+	unsigned long long int processticks = ((unsigned long long int)_atari_4ba_at_prg_start * CLK_TCK) / 200;
+	processticks = libcticks - processticks;
+
+	// For a single threaded system like the atari, only tms_utime is meaningful.
+	buf->tms_utime = (clock_t)processticks;
+	buf->tms_stime = 0;
+	buf->tms_cutime = 0;
+	buf->tms_cstime = 0;
+	return (clock_t)libcticks;
+}
diff --git a/libgloss/m68k/atari/atari-tos.ld b/libgloss/m68k/atari/atari-tos.ld
new file mode 100644
index 000000000..6bbecc261
--- /dev/null
+++ b/libgloss/m68k/atari/atari-tos.ld
@@ -0,0 +1,160 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+OUTPUT_FORMAT("elf32-m68k", "elf32-m68k", "elf32-m68k")
+OUTPUT_ARCH(m68k)
+INPUT (crti.o crtn.o crt0.o)
+
+SECTIONS
+{
+	/*
+	Start all addresses at 0.
+	That way, the relocation is a bit easier when converting to prg.
+	*/
+	. = 0x0;
+	.text : {
+		*crti.o(.init)
+		*crtbegin.o(.init)
+		*crtend.o(.init)
+		*crtn.o(.init)
+		*crt0.o(.init)
+		*crti.o(.fini)
+		*crtbegin.o(.fini)
+		*crtend.o(.fini)
+		*crtn.o(.fini)
+		*(.text*)
+		*(.gnu.warning)
+		FILL(0x0000);
+		. = ALIGN(0x4);
+	}
+	/*
+		There is no rodata in a prg file, so we store all data in .data section.
+	*/
+	.data BLOCK(0x4) : {
+		__DATA_SEGMENT__ = .;
+		*(.data*);
+		. = ALIGN(0x4);
+		*(.rodata*);
+		. = ALIGN(0x4);
+
+		*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*)
+		*(.eh_frame) *(.eh_frame.*)
+		*(.sframe) *(.sframe.*)
+		*(.gcc_except_table .gcc_except_table.*)
+		*(.gnu_extab*)
+
+		/*
+			All constructors
+		*/
+		KEEP (*crtbegin.o(.ctors))
+		KEEP (*crtbegin?.o(.ctors))
+		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
+		KEEP (*(SORT(.ctors.*)))
+		KEEP (*(.ctors))
+
+		/*
+			All destructors
+		*/
+		KEEP (*crtbegin.o(.dtors))
+		KEEP (*crtbegin?.o(.dtors))
+		KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
+		KEEP (*(SORT(.dtors.*)))
+		KEEP (*(.dtors))
+
+		FILL(0x0000);
+		. = ALIGN(0x4);
+	}
+	.bss BLOCK(0x4) : {
+		__BSS_SEGMENT__ = .;
+		*(.bss);
+		. = ALIGN(0x4);
+		*(COMMON);
+		. = ALIGN(0x4);
+	}
+	__BSS_SEGMENT_END = .;
+
+	/*
+	Atari Prg header.
+	We don't care what the address is for this.
+	*/
+	.prgheader : {
+		SHORT(0x601a); 								/* Branch to start of the program  (must be 0x601a, used as magic number!) */
+		LONG(__DATA_SEGMENT__);						/* Length of the TEXT segment */
+		LONG(__BSS_SEGMENT__ - __DATA_SEGMENT__);	/* Length of the DATA segment */
+		LONG(__BSS_SEGMENT_END - __BSS_SEGMENT__);	/* Length of the BSS segment */
+		LONG(0);									/* Length of the symbol table */
+		LONG(0);									/* Reserved, should be 0 */
+		LONG(0);									/* Program flags */
+		SHORT(0);									/*  0 = Relocation info present */
+	}
+
+	.note.gnu.build-id  : { *(.note.gnu.build-id) }
+	.interp         : { *(.interp) }
+	.hash           : { *(.hash) }
+	.gnu.hash       : { *(.gnu.hash) }
+	.dynsym         : { *(.dynsym) }
+	.dynstr         : { *(.dynstr) }
+	.gnu.version    : { *(.gnu.version) }
+	.gnu.version_d  : { *(.gnu.version_d) }
+	.gnu.version_r  : { *(.gnu.version_r) }
+
+	/* Stabs debugging sections.  */
+	.stab          0 : { *(.stab) }
+	.stabstr       0 : { *(.stabstr) }
+	.stab.excl     0 : { *(.stab.excl) }
+	.stab.exclstr  0 : { *(.stab.exclstr) }
+	.stab.index    0 : { *(.stab.index) }
+	.stab.indexstr 0 : { *(.stab.indexstr) }
+	.comment 0 (INFO) : { *(.comment); LINKER_VERSION; }
+	.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
+
+	/* DWARF debug sections.
+		Symbols in the DWARF debugging sections are relative to the beginning
+		of the section so we begin them at 0.
+	*/
+	/* DWARF 1.  */
+	.debug          0 : { *(.debug) }
+	.line           0 : { *(.line) }
+
+	/* GNU DWARF 1 extensions.  */
+	.debug_srcinfo  0 : { *(.debug_srcinfo) }
+	.debug_sfnames  0 : { *(.debug_sfnames) }
+
+	/* DWARF 1.1 and DWARF 2.  */
+	.debug_aranges  0 : { *(.debug_aranges) }
+	.debug_pubnames 0 : { *(.debug_pubnames) }
+
+	/* DWARF 2.  */
+	.debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
+	.debug_abbrev   0 : { *(.debug_abbrev) }
+	.debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end) }
+	.debug_frame    0 : { *(.debug_frame) }
+	.debug_str      0 : { *(.debug_str) }
+	.debug_loc      0 : { *(.debug_loc) }
+	.debug_macinfo  0 : { *(.debug_macinfo) }
+
+	/* SGI/MIPS DWARF 2 extensions.  */
+	.debug_weaknames 0 : { *(.debug_weaknames) }
+	.debug_funcnames 0 : { *(.debug_funcnames) }
+	.debug_typenames 0 : { *(.debug_typenames) }
+	.debug_varnames  0 : { *(.debug_varnames) }
+
+	/* DWARF 3.  */
+	.debug_pubtypes 0 : { *(.debug_pubtypes) }
+	.debug_ranges   0 : { *(.debug_ranges) }
+
+	/* DWARF 5.  */
+	.debug_addr     0 : { *(.debug_addr) }
+	.debug_line_str 0 : { *(.debug_line_str) }
+	.debug_loclists 0 : { *(.debug_loclists) }
+	.debug_macro    0 : { *(.debug_macro) }
+	.debug_names    0 : { *(.debug_names) }
+	.debug_rnglists 0 : { *(.debug_rnglists) }
+	.debug_str_offsets 0 : { *(.debug_str_offsets) }
+	.debug_sup      0 : { *(.debug_sup) }
+	.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
+	/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) *(.gnu_object_only) }
+
+}
\ No newline at end of file
diff --git a/libgloss/m68k/atari/atari-tos.specs b/libgloss/m68k/atari/atari-tos.specs
new file mode 100644
index 000000000..00781c4fc
--- /dev/null
+++ b/libgloss/m68k/atari/atari-tos.specs
@@ -0,0 +1,10 @@ 
+#	Copyright (C) 2025 Mikael Hildenborg
+#	SPDX-License-Identifier: BSD-2-Clause
+
+# Atari gcc specs
+
+*link:
++ --emit-relocs --no-warn-rwx-segments -static --no-warn-execstack --undefined=__errno -T atari-tos.ld
+
+*lib:
++ -latari-tos
diff --git a/libgloss/m68k/atari/atari-traps.c b/libgloss/m68k/atari/atari-traps.c
new file mode 100644
index 000000000..c387ff144
--- /dev/null
+++ b/libgloss/m68k/atari/atari-traps.c
@@ -0,0 +1,311 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "atari-traps.h"
+
+// Store stack pointer in a3, that by Atari documentation will be left untouched by the trap call.
+#define TRAP_BEGIN "move.l		%%a7, %%a3\n\t"
+// Make trap call and then restore the stack pointer from the stored value in a3
+#define TRAP_FUNC(num, func) "move.w		#" #func ", %%a7@-\n\ttrap		#" #num "\n\tmove.l		%%a3, %%a7\n\t"
+// Registers d1,d2 and a0, a1, a2 may be affected by trap #1 calls. Register a3 is used to store/restore a7
+#define CLOBBER_REG "d1", "d2", "a0", "a1", "a2", "a3"
+
+unsigned int trap1_e(unsigned short bios_drive)
+{
+	register unsigned int bios_mounted_drives asm ("d0") = 0;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0xe)
+		: "=r" (bios_mounted_drives)
+		: "r" (bios_drive)
+		: CLOBBER_REG);
+	return bios_mounted_drives;
+}
+
+unsigned short trap1_19(void)
+{
+	register unsigned short bios_drive asm ("d0") = 0;
+	__asm__ volatile (
+		TRAP_BEGIN
+		TRAP_FUNC(1, 0x19)
+		: "=r" (bios_drive)
+		:
+		: CLOBBER_REG);
+	return bios_drive;
+}
+
+unsigned short trap1_2a(void)
+{
+	register unsigned short date asm ("d0") = 0;
+	__asm__ volatile (
+		TRAP_BEGIN
+		TRAP_FUNC(1, 0x2a)
+		: "=r" (date)
+		:
+		: CLOBBER_REG);
+	return date;
+}
+
+int trap1_2b(unsigned short date)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x2b)
+		: "=r" (result)
+		:"r" (date)
+		: CLOBBER_REG);
+	return result;
+}
+
+unsigned short trap1_2c(void)
+{
+	register unsigned short time asm ("d0") = 0;
+	__asm__ volatile (
+		TRAP_BEGIN
+		TRAP_FUNC(1, 0x2c)
+		: "=r" (time)
+		:
+		: CLOBBER_REG);
+	return time;
+}
+
+int trap1_2d(unsigned short time)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x2d)
+		: "=r" (result)
+		:"r" (time)
+		: CLOBBER_REG);
+	return result;
+}
+
+
+struct DTA* trap1_2f(unsigned short bios_handle)
+{
+	register struct DTA* result asm ("d0") = (struct DTA*)-1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x2f)
+		: "=r" (result)
+		: "r" (bios_handle)
+		: CLOBBER_REG);
+	return result;
+}
+
+
+int trap1_39(const char* bios_path)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x39)
+		: "=r" (result)
+		: "r" (bios_path)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_3a(const char* bios_path)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x3a)
+		: "=r" (result)
+		: "r" (bios_path)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_3b(const char* bios_path)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x3b)
+		: "=r" (result)
+		: "r" (bios_path)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_3c(const char* bios_path, unsigned short bios_attrib)
+{
+	register int bios_handle asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%2, %%a7@-\n\t"
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x3c)
+		: "=r" (bios_handle)
+		: "r" (bios_path), "r" (bios_attrib)
+		: CLOBBER_REG);
+	return bios_handle;
+}
+
+int trap1_3d(const char* bios_path, unsigned short bios_mode)
+{
+	register int bios_handle asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%2, %%a7@-\n\t"
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x3d)
+		: "=r" (bios_handle)
+		: "r" (bios_path), "r" (bios_mode)
+		: CLOBBER_REG);
+	return bios_handle;
+}
+
+int trap1_3e(unsigned short bios_handle)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x3e)
+		: "=r" (result)
+		: "r" (bios_handle)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_3f(unsigned short bios_handle, int length, void* buf)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%3, %%a7@-\n\t"
+		"move.l		%2, %%a7@-\n\t"
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x3f)
+		: "=r" (result)
+		: "r" (bios_handle), "r" (length), "r" (buf)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_40(unsigned short bios_handle, int length, const void* buf)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%3, %%a7@-\n\t"
+		"move.l		%2, %%a7@-\n\t"
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x40)
+		: "=r" (result)
+		: "r" (bios_handle), "r" (length), "r" (buf)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_41(const char* bios_path)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x41)
+		: "=r" (result)
+		: "r" (bios_path)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_42(unsigned int file_position, unsigned short bios_handle, unsigned short bios_mode)
+{
+	register int new_position asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%3, %%a7@-\n\t"
+		"move.w		%2, %%a7@-\n\t"
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x42)
+		: "=r" (new_position)
+		: "r" (file_position), "r" (bios_handle), "r" (bios_mode)
+		: CLOBBER_REG);
+	return new_position;
+}
+
+int trap1_47(char* buf, unsigned short bios_drive)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.w		%2, %%a7@-\n\t"
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x47)
+		: "=r" (result)
+		: "r" (buf), "r" (bios_drive)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_4b(unsigned short mode, const char* file_name, const char* cmdline, const char* envstring)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%4, %%a7@-\n\t"
+		"move.l		%3, %%a7@-\n\t"
+		"move.l		%2, %%a7@-\n\t"
+		"move.w		%1, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x4b)
+		: "=r" (result)
+		: "r" (mode), "r" (file_name), "r" (cmdline), "r" (envstring)
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap1_56(const char* oldname, const char* newname)
+{
+	register int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%2, %%a7@-\n\t"
+		"move.l		%1, %%a7@-\n\t"
+		"move.w		#0, %%a7@-\n\t"
+		TRAP_FUNC(1, 0x56)
+		: "=r" (result)
+		: "r" (oldname), "r" (newname)
+		: CLOBBER_REG);
+	return result;
+}
+
+unsigned int trap14_11(void)
+{
+	register unsigned int result asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		TRAP_FUNC(14, 0x11)
+		: "=r" (result)
+		:
+		: CLOBBER_REG);
+	return result;
+}
+
+int trap14_26(int (*callback)(void))
+{
+	register int callback_return asm ("d0") = -1;
+	__asm__ volatile (
+		TRAP_BEGIN
+		"move.l		%1, %%a7@-\n\t"
+		TRAP_FUNC(14, 0x26)
+		: "=r" (callback_return)
+		: "r" (callback)
+		: CLOBBER_REG);
+	return callback_return;
+}
diff --git a/libgloss/m68k/atari/atari-traps.h b/libgloss/m68k/atari/atari-traps.h
new file mode 100644
index 000000000..01edb7062
--- /dev/null
+++ b/libgloss/m68k/atari/atari-traps.h
@@ -0,0 +1,84 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#ifndef TRAPS_DEFINED
+#define TRAPS_DEFINED
+
+#include "atari-gem_basepage.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+	Only trap calls used by atari libgloss is included here.
+*/
+
+unsigned int trap1_e(unsigned short bios_drive);
+
+// Returned drive starts at 0 for A
+unsigned short trap1_19(void);
+
+unsigned short trap1_2a(void);
+
+int trap1_2b(unsigned short date);
+
+unsigned short trap1_2c(void);
+
+int trap1_2d(unsigned short time);
+
+struct DTA* trap1_2f(unsigned short bios_handle);
+
+int trap1_39(const char* bios_path);
+
+int trap1_3a(const char* bios_path);
+
+int trap1_3b(const char* bios_path);
+
+int trap1_3c(const char* bios_path, unsigned short bios_attrib);
+
+int trap1_3d(const char* bios_path, unsigned short bios_mode);
+
+#define GSH_BIOSCON		0xFFFF
+#define GSH_BIOSAUX		0xFFFE
+#define GSH_BIOSPRN		0xFFFD
+#define GSH_BIOSMIDIIN	0xFFFC
+#define GSH_BIOSMIDIOUT	0xFFFB
+#define GSH_CONIN		0x00
+#define GSH_CONOUT		0x01
+#define GSH_AUX			0x02
+#define GSH_PRN			0x03
+
+int trap1_3e(unsigned short bios_handle);
+
+int trap1_3f(unsigned short bios_handle, int length, void* buf);
+
+int trap1_40(unsigned short bios_handle, int length, const void* buf);
+
+int trap1_41(const char* bios_path);
+
+int trap1_42(unsigned int file_position, unsigned short bios_handle, unsigned short bios_mode);
+
+// bios_drive 0 is default drive, and 1 and upwards is A...
+int trap1_47(char* buf, unsigned short bios_drive);
+
+#define PE_LOADGO		0
+#define PE_LOAD			3
+#define PE_GO			4
+#define PE_BASEPAGE		5
+#define PE_GOTHENFREE	6
+int trap1_4b(unsigned short mode, const char* file_name, const char* cmdline, const char* envstring);
+
+int trap1_56(const char* oldname, const char* newname);
+
+unsigned int trap14_11(void);
+
+int trap14_26(int (*callback)(void));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TRAPS_DEFINED
diff --git a/libgloss/m68k/atari/atari-unlink.c b/libgloss/m68k/atari/atari-unlink.c
new file mode 100644
index 000000000..cacafcf5e
--- /dev/null
+++ b/libgloss/m68k/atari/atari-unlink.c
@@ -0,0 +1,19 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+int unlink(char *path)
+{
+	int err = trap1_41(path);
+	if (err < 0)
+	{
+		gem_error_to_errno(err);
+		return -1;
+	}
+	return 0;
+}
diff --git a/libgloss/m68k/atari/atari-wait.c b/libgloss/m68k/atari/atari-wait.c
new file mode 100644
index 000000000..946419305
--- /dev/null
+++ b/libgloss/m68k/atari/atari-wait.c
@@ -0,0 +1,16 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include "config.h"
+#include <errno.h>
+#include "libnosys/warning.h"
+
+int wait(int *status)
+{
+    errno = ENOSYS;
+    return -1;
+}
+
+stub_warning(wait)
diff --git a/libgloss/m68k/atari/atari-write.c b/libgloss/m68k/atari/atari-write.c
new file mode 100644
index 000000000..9d6e59ed8
--- /dev/null
+++ b/libgloss/m68k/atari/atari-write.c
@@ -0,0 +1,87 @@ 
+/*
+	Copyright (C) 2025 Mikael Hildenborg
+	SPDX-License-Identifier: BSD-2-Clause
+*/
+
+#include <unistd.h>
+#include <_ansi.h>
+#include "atari-gem_errno.h"
+#include "atari-traps.h"
+
+const char* lineEnding = "\r\n";
+
+int writeUntilDoneOrError(int fd, size_t len, const char* buf)
+{
+	size_t written = 0;
+	if (len == 0)
+	{
+		return 0;
+	}
+	do
+	{
+		int n = trap1_40((unsigned short)fd, len, buf);
+		if (n < 0)
+		{
+			gem_error_to_errno(n);
+			return -1;
+		}
+		written += n;
+		buf += n;
+	} while (written < len);
+	return 0;
+}
+
+
+_READ_WRITE_RETURN_TYPE write(int fd, const void *buf, size_t nbytes)
+{
+	int numWritten = GEM_EIHNDL;
+	if (fd >= 0)
+	{
+		if (fd == 2)
+		{
+			fd = GSH_CONOUT; // Use console out for stderr.
+		}
+		if (fd == GSH_CONOUT)
+		{
+			// When we write to stdout on Atari, we must add a \r after \n to
+			// get the correct C output behaviour.
+			const char* stream = (const char*)buf;
+			size_t lastWrite = 0;
+			for (size_t i = 0; i < nbytes; ++i)
+			{
+				if (stream[i] == '\n')
+				{
+					int len = i - lastWrite; // length up to but not including \n
+					if (writeUntilDoneOrError(fd, len, stream + lastWrite) < 0)
+					{
+						return -1;
+					}
+					if (writeUntilDoneOrError(fd, 2, lineEnding) < 0)
+					{
+						return -1;
+					}
+					lastWrite = i + 1;	// Include the \n
+				}
+			}
+			if (lastWrite < nbytes)
+			{
+				int len = nbytes - lastWrite;
+				if (writeUntilDoneOrError(fd, len, stream + lastWrite) < 0)
+				{
+					return -1;
+				}
+			}
+			numWritten = nbytes;
+		}
+		else
+		{
+			numWritten = trap1_40((unsigned short)fd, nbytes, buf);
+		}
+	}
+	if (numWritten < 0)
+	{
+		gem_error_to_errno(numWritten);
+		return -1;
+	}
+	return numWritten;
+}
diff --git a/newlib/configure.host b/newlib/configure.host
index 89564c0b6..960cca215 100644
--- a/newlib/configure.host
+++ b/newlib/configure.host
@@ -252,7 +252,6 @@  case "${host_cpu}" in
   mips*)
 	machine_dir=mips
 	libm_machine_dir=mips
-	newlib_cflags="${newlib_cflags}"
 	;;
   mmix)
 	;;
@@ -769,6 +768,10 @@  newlib_cflags="${newlib_cflags} -DCLOCK_PROVIDED -DMALLOC_PROVIDED -DEXIT_PROVID
 	newlib_cflags="${newlib_cflags} -DHAVE_RENAME -DHAVE_SYSTEM -DMISSING_SYSCALL_NAMES"
 	syscall_dir=
 	;;
+  m68k-atari-elf)
+	newlib_cflags="${newlib_cflags} -DHAVE_RENAME -DMISSING_SYSCALL_NAMES"
+	syscall_dir=
+	;;
   mcore-*-*)
 	syscall_dir=syscalls
 	;;
-- 
2.43.0