diff --git a/CMakeLists.txt b/CMakeLists.txt index 37733865f..ded5b1bca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,8 @@ endif() # The C flags are final here: an indirect tail call under -mregparm loses its # pointer argument unless sibling calls are off. m68k cross builds only. include("${CMAKE_SOURCE_DIR}/cmake/check-regparm-sibcall.cmake") +# And the callbacks a driver from another build calls stay on the stack. +include("${CMAKE_SOURCE_DIR}/cmake/check-anxs2ext-abi.cmake") # ---------------------------------------------------------------- options --- diff --git a/cmake/check-anxs2ext-abi.cmake b/cmake/check-anxs2ext-abi.cmake new file mode 100644 index 000000000..2e9a3d070 --- /dev/null +++ b/cmake/check-anxs2ext-abi.cmake @@ -0,0 +1,69 @@ +# The anxs2ext.h callbacks take their arguments on the stack. +# +# include(check-anxs2ext-abi) -- at configure, m68k cross builds only +# +# RxDirect, RxFilled and TxFlags are called by a driver built separately from +# the library, often from another release, and every release before -mregparm +# pushed their arguments. d6b6a7e7 left the typedefs unpinned: its library +# read a0/d0 for what a 144042c driver had pushed, RxDirect answered an address +# made of its own code bytes, and on a PiStorm32 A1200 neither card came up. +# This compiles a call through each typedef with the tree's flags and stops +# the configure unless the caller pops what it pushed: 8, 16 and 4 bytes. +# +# SPDX-License-Identifier: MIT + +if(NOT CMAKE_CROSSCOMPILING) + return() +endif() + +set(_s2x_dir "${CMAKE_BINARY_DIR}/check-anxs2ext-abi") +file(MAKE_DIRECTORY "${_s2x_dir}") +file(WRITE "${_s2x_dir}/probe.c" [=[ +#include +UBYTE *probe_rx_direct(AnxdS2RxDirect f, APTR io, ULONG len) +{ + return f(io, len) + 1; +} +ULONG probe_rx_filled(AnxdS2RxFilled f, APTR io, ULONG len, ULONG sum, UBYTE fl) +{ + f(io, len, sum, fl); + return len + 1; +} +UBYTE probe_tx_flags(AnxdS2TxFlags f, APTR io) +{ + return (UBYTE)(f(io) + 1); +} +]=]) + +# The flags the objects are built with, less LTO: -S has to produce assembly. +separate_arguments(_s2x_flags UNIX_COMMAND + "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE}") +list(REMOVE_ITEM _s2x_flags -flto) + +execute_process( + COMMAND "${CMAKE_C_COMPILER}" ${_s2x_flags} + "-I${CMAKE_SOURCE_DIR}/include" -S + -o "${_s2x_dir}/probe.s" "${_s2x_dir}/probe.c" + RESULT_VARIABLE _s2x_rc + ERROR_VARIABLE _s2x_err) +if(NOT _s2x_rc EQUAL 0) + message(FATAL_ERROR "check-anxs2ext-abi: the probe did not compile:\n${_s2x_err}") +endif() + +file(READ "${_s2x_dir}/probe.s" _s2x_asm) + +foreach(_s2x_pair "rx_direct;8" "rx_filled;16" "tx_flags;4") + list(GET _s2x_pair 0 _s2x_fn) + list(GET _s2x_pair 1 _s2x_n) + string(REGEX MATCH "_probe_${_s2x_fn}:[^:]*" _s2x_body "${_s2x_asm}") + if(NOT _s2x_body MATCHES + "jsr[^\n]*\n(.*\n)?[ \t]*(addq\\.[wl][ \t]+#${_s2x_n},%?sp|add\\.[wl][ \t]+#${_s2x_n},%?sp|lea[ \t]+\\(${_s2x_n},%?sp\\),%?sp|lea[ \t]+${_s2x_n}\\(%?sp\\),%?sp|lea[ \t]+%?sp@\\(${_s2x_n}\\),%?sp)") + message(FATAL_ERROR + "check-anxs2ext-abi: a call through AnxdS2* (${_s2x_fn}) does not " + "pass its arguments on the stack under -mregparm=${AMINETXDUO_REGPARM}; " + "a driver from another build would read the wrong registers. " + "See ${_s2x_dir}/probe.s") + endif() +endforeach() + +message(STATUS "check-anxs2ext-abi: driver callbacks pass on the stack (regparm ${AMINETXDUO_REGPARM})") diff --git a/cmake/toolchain-m68k-amigaos.cmake b/cmake/toolchain-m68k-amigaos.cmake index dd940c72c..3c72a671a 100644 --- a/cmake/toolchain-m68k-amigaos.cmake +++ b/cmake/toolchain-m68k-amigaos.cmake @@ -385,6 +385,10 @@ string(REPLACE ";" " " AMIGA_ARCH_FLAGS_STR "${AMIGA_ARCH_FLAGS}") # whatever -mregparm says; # - user-supplied hooks (loghook.c, errno.c, netmonitor.c) pin a0/a1/a2, so a # caller compiled the ordinary way is still called correctly; +# - the aminetxduo/anxs2ext.h callbacks a driver calls into the library +# (RxDirect, RxFilled, TxFlags) carry __stdargs, because the driver may be +# from another build. MISSED when the option landed: a 144042c anxnet.device +# under a d6b6a7e7 library moved no frames. cmake/check-anxs2ext-abi.cmake; # - the hand-written routines in src/net68k and src/crypto68k read the stack, # and every C declaration of one carries AMIGA_ASM_ARGS # (__attribute__((__stkparm__))); see include/aminetxduo/asm_abi.h. diff --git a/include/aminetxduo/anxs2ext.h b/include/aminetxduo/anxs2ext.h index 9dd0e0d76..b2e6d38a8 100644 --- a/include/aminetxduo/anxs2ext.h +++ b/include/aminetxduo/anxs2ext.h @@ -18,10 +18,24 @@ wire together. Negotiated as ANXD_S2F_TX_MORE. */ #define ANXD_S2_TXF_MORE 0x40 -typedef UBYTE *(*AnxdS2RxDirect)(APTR ios2_data, ULONG len); -typedef VOID (*AnxdS2RxFilled)(APTR ios2_data, ULONG len, ULONG sum, - UBYTE flags); -typedef UBYTE (*AnxdS2TxFlags)(APTR ios2_data); +/* + * The callbacks below cross between separately built binaries, a driver from + * one release calling a library from another, so their arguments go on the + * stack whatever the compiler was told. A GCC for AmigaOS predefines + * __stdargs as that pin; a compiler without it already passes on the stack. + * Unpinned under -mregparm, a library read a0/d0 for what an older driver had + * pushed, and RxDirect answered an address made of its own code bytes. + */ +#if defined(__GNUC__) && defined(__stdargs) +#define ANXD_S2_STDARGS __stdargs +#else +#define ANXD_S2_STDARGS +#endif + +typedef ANXD_S2_STDARGS UBYTE *(*AnxdS2RxDirect)(APTR ios2_data, ULONG len); +typedef ANXD_S2_STDARGS VOID (*AnxdS2RxFilled)(APTR ios2_data, ULONG len, + ULONG sum, UBYTE flags); +typedef ANXD_S2_STDARGS UBYTE (*AnxdS2TxFlags)(APTR ios2_data); /* * ONE VERSIONED NEGOTIATION TAG. diff --git a/src/sana2/sana2_copy.c b/src/sana2/sana2_copy.c index 234b1ff38..294da019f 100644 --- a/src/sana2/sana2_copy.c +++ b/src/sana2/sana2_copy.c @@ -545,7 +545,7 @@ BOOL ami_sana2_copy_from_buff(register APTR to __asm("a0"), * A slot that cannot answer safely declines and the device takes the staging * path. Interrupt level, copybuff.doc constraints. */ -UBYTE *ami_sana2_rx_direct(APTR ios2_data, ULONG len) +AMIGA_ASM_ARGS UBYTE *ami_sana2_rx_direct(APTR ios2_data, ULONG len) { AmiRxSlot *slot = (AmiRxSlot *)ios2_data; @@ -564,7 +564,8 @@ UBYTE *ami_sana2_rx_direct(APTR ios2_data, ULONG len) * verifier walks the frame); VERIFIED is kept whole for the * reader, which only sees them from a device that accepted RX_VERIFIED. */ -VOID ami_sana2_rx_filled(APTR ios2_data, ULONG len, ULONG sum, UBYTE flags) +AMIGA_ASM_ARGS VOID ami_sana2_rx_filled(APTR ios2_data, ULONG len, ULONG sum, + UBYTE flags) { AmiRxSlot *slot = (AmiRxSlot *)ios2_data; diff --git a/src/sana2/sana2_internal.h b/src/sana2/sana2_internal.h index b3568a10c..f6ad252f7 100644 --- a/src/sana2/sana2_internal.h +++ b/src/sana2/sana2_internal.h @@ -18,6 +18,7 @@ #include "aminetxduo/config.h" #include "aminetxduo/compat.h" #include "aminetxduo/anxs2ext.h" +#include "aminetxduo/asm_abi.h" #include "sana2_device.h" @@ -978,9 +979,11 @@ struct AmiSana2If /* ------------------------------------------------------------- internals */ -/* sana2_copy.c, called by the device in m68k register convention. */ -UBYTE *ami_sana2_rx_direct(APTR ios2_data, ULONG len); -VOID ami_sana2_rx_filled(APTR ios2_data, ULONG len, ULONG sum, UBYTE summed); +/* sana2_copy.c, called by the device with its arguments on the stack + (aminetxduo/anxs2ext.h). */ +AMIGA_ASM_ARGS UBYTE *ami_sana2_rx_direct(APTR ios2_data, ULONG len); +AMIGA_ASM_ARGS VOID ami_sana2_rx_filled(APTR ios2_data, ULONG len, ULONG sum, + UBYTE summed); /* Bytes a known vendor driver's card holds from the wire, by name; 0 for any other (sana2_device.c). */ @@ -994,7 +997,7 @@ BOOL ami_sana2_copy_to_buff(register APTR to __asm("a0"), register APTR from __asm("a1"), register ULONG len __asm("d0")); BOOL ami_sana2_tx_pseudo_sum(NX_PACKET *pkt); -UBYTE ami_sana2_tx_flags(APTR ios2_data); +AMIGA_ASM_ARGS UBYTE ami_sana2_tx_flags(APTR ios2_data); #ifdef AMINETXDUO_TX_RUN /* The flush request came back on tx_port: the device queued it rather than finishing it inside BeginIO() (sana2_tx.c). */ diff --git a/src/sana2/sana2_txmeta.c b/src/sana2/sana2_txmeta.c index e61f27813..172abf395 100644 --- a/src/sana2/sana2_txmeta.c +++ b/src/sana2/sana2_txmeta.c @@ -5,7 +5,7 @@ /* A driver may call this while advancing a queued write from interrupt context. ios2_Data is the opener-owned slot for every request we submit, so the entire callback is deliberately one guarded byte load. */ -UBYTE ami_sana2_tx_flags(APTR ios2_data) +AMIGA_ASM_ARGS UBYTE ami_sana2_tx_flags(APTR ios2_data) { const AmiTxSlot *slot = (const AmiTxSlot *)ios2_data;