diff options
Diffstat (limited to 'libhw_cr')
-rw-r--r-- | libhw_cr/CMakeLists.txt | 1 | ||||
-rw-r--r-- | libhw_cr/host_alarmclock.c | 43 | ||||
-rw-r--r-- | libhw_cr/host_include/libhw/host_alarmclock.h | 5 | ||||
-rw-r--r-- | libhw_cr/host_include/libhw/host_net.h | 9 | ||||
-rw-r--r-- | libhw_cr/host_net.c | 339 | ||||
-rw-r--r-- | libhw_cr/host_util.c | 128 | ||||
-rw-r--r-- | libhw_cr/host_util.h | 41 | ||||
-rw-r--r-- | libhw_cr/rp2040_dma.c | 109 | ||||
-rw-r--r-- | libhw_cr/rp2040_dma.h | 142 | ||||
-rw-r--r-- | libhw_cr/rp2040_gpioirq.c | 4 | ||||
-rw-r--r-- | libhw_cr/rp2040_hwspi.c | 218 | ||||
-rw-r--r-- | libhw_cr/rp2040_hwtimer.c | 29 | ||||
-rw-r--r-- | libhw_cr/rp2040_include/libhw/rp2040_hwspi.h | 6 | ||||
-rw-r--r-- | libhw_cr/rp2040_include/libhw/w5500.h | 13 | ||||
-rw-r--r-- | libhw_cr/w5500.c | 257 | ||||
-rw-r--r-- | libhw_cr/w5500_ll.c | 116 | ||||
-rw-r--r-- | libhw_cr/w5500_ll.h | 201 |
17 files changed, 1035 insertions, 626 deletions
diff --git a/libhw_cr/CMakeLists.txt b/libhw_cr/CMakeLists.txt index ba20b26..9dd6a27 100644 --- a/libhw_cr/CMakeLists.txt +++ b/libhw_cr/CMakeLists.txt @@ -24,6 +24,7 @@ if (PICO_PLATFORM STREQUAL "rp2040") rp2040_hwspi.c rp2040_hwtimer.c w5500.c + w5500_ll.c ) target_link_libraries(libhw_cr INTERFACE hardware_gpio diff --git a/libhw_cr/host_alarmclock.c b/libhw_cr/host_alarmclock.c index 9eedec2..0c42677 100644 --- a/libhw_cr/host_alarmclock.c +++ b/libhw_cr/host_alarmclock.c @@ -5,9 +5,12 @@ */ #include <errno.h> -#include <error.h> #include <signal.h> +#define error __error +#include <error.h> +#undef error + #include <libcr/coroutine.h> #include <libmisc/assert.h> @@ -19,15 +22,15 @@ #include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_ns_time() */ -LO_IMPLEMENTATION_C(alarmclock, struct hostclock, hostclock, static); +LO_IMPLEMENTATION_C(alarmclock, struct hostclock, hostclock); -static uint64_t hostclock_get_time_ns(struct hostclock *alarmclock) { +uint64_t hostclock_get_time_ns(struct hostclock *alarmclock) { assert(alarmclock); struct timespec ts; if (clock_gettime(alarmclock->clock_id, &ts) != 0) - error(1, errno, "clock_gettime(%d)", (int)alarmclock->clock_id); + __error(1, errno, "clock_gettime(%d)", (int)alarmclock->clock_id); return ns_from_host_ns_time(ts); } @@ -49,18 +52,18 @@ static void hostclock_handle_sig_alarm(int LM_UNUSED(sig), siginfo_t *info, void if (alarmclock->queue) { struct itimerspec alarmspec = { .it_value = ns_to_host_ns_time(alarmclock->queue->fire_at_ns), - .it_interval = {0}, + .it_interval = {}, }; if (timer_settime(alarmclock->timer_id, TIMER_ABSTIME, &alarmspec, NULL) != 0) - error(1, errno, "timer_settime"); + __error(1, errno, "timer_settime"); } } -static bool hostclock_add_trigger(struct hostclock *alarmclock, - struct alarmclock_trigger *trigger, - uint64_t fire_at_ns, - void (*cb)(void *), - void *cb_arg) { +void hostclock_add_trigger(struct hostclock *alarmclock, + struct alarmclock_trigger *trigger, + uint64_t fire_at_ns, + void (*cb)(void *), + void *cb_arg) { assert(alarmclock); assert(trigger); assert(fire_at_ns); @@ -72,6 +75,7 @@ static bool hostclock_add_trigger(struct hostclock *alarmclock, trigger->cb_arg = cb_arg; bool saved = cr_save_and_disable_interrupts(); + struct alarmclock_trigger **dst = &alarmclock->queue; while (*dst && fire_at_ns >= (*dst)->fire_at_ns) dst = &(*dst)->next; @@ -80,6 +84,7 @@ static bool hostclock_add_trigger(struct hostclock *alarmclock, if (*dst) (*dst)->prev = trigger; *dst = trigger; + if (!alarmclock->initialized) { struct sigevent how_to_notify = { .sigev_notify = SIGEV_SIGNAL, @@ -93,26 +98,26 @@ static bool hostclock_add_trigger(struct hostclock *alarmclock, .sa_sigaction = hostclock_handle_sig_alarm, }; if (sigaction(how_to_notify.sigev_signo, &action, NULL) != 0) - error(1, errno, "sigaction"); + __error(1, errno, "sigaction"); if (timer_create(alarmclock->clock_id, &how_to_notify, &alarmclock->timer_id) != 0) - error(1, errno, "timer_create(%d)", (int)alarmclock->clock_id); + __error(1, errno, "timer_create(%d)", (int)alarmclock->clock_id); alarmclock->initialized = true; } + if (alarmclock->queue == trigger) { struct itimerspec alarmspec = { .it_value = ns_to_host_ns_time(trigger->fire_at_ns), - .it_interval = {0}, + .it_interval = {}, }; if (timer_settime(alarmclock->timer_id, TIMER_ABSTIME, &alarmspec, NULL) != 0) - error(1, errno, "timer_settime"); + __error(1, errno, "timer_settime"); } - cr_restore_interrupts(saved); - return false; + cr_restore_interrupts(saved); } -static void hostclock_del_trigger(struct hostclock *alarmclock, - struct alarmclock_trigger *trigger) { + void hostclock_del_trigger(struct hostclock *alarmclock, + struct alarmclock_trigger *trigger) { assert(alarmclock); assert(trigger); diff --git a/libhw_cr/host_include/libhw/host_alarmclock.h b/libhw_cr/host_include/libhw/host_alarmclock.h index 0cb8d30..2ddb054 100644 --- a/libhw_cr/host_include/libhw/host_alarmclock.h +++ b/libhw_cr/host_include/libhw/host_alarmclock.h @@ -7,11 +7,10 @@ #ifndef _LIBHW_HOST_ALARMCLOCK_H_ #define _LIBHW_HOST_ALARMCLOCK_H_ -#include <stdbool.h> /* for bool */ -#include <time.h> /* for clockid_t, timer_t */ +#include <time.h> /* for clockid_t, timer_t */ -#include <libmisc/private.h> #include <libhw/generic/alarmclock.h> +#include <libmisc/private.h> struct hostclock { clockid_t clock_id; diff --git a/libhw_cr/host_include/libhw/host_net.h b/libhw_cr/host_include/libhw/host_net.h index a16ed01..6ff2779 100644 --- a/libhw_cr/host_include/libhw/host_net.h +++ b/libhw_cr/host_include/libhw/host_net.h @@ -13,13 +13,16 @@ #include <libhw/generic/net.h> +/* TCP connection *************************************************************/ + struct _hostnet_tcp_conn { BEGIN_PRIVATE(LIBHW_HOST_NET_H); int fd; uint64_t read_deadline_ns; END_PRIVATE(LIBHW_HOST_NET_H); }; -LO_IMPLEMENTATION_H(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp); + +/* TCP listener ***************************************************************/ struct hostnet_tcp_listener { BEGIN_PRIVATE(LIBHW_HOST_NET_H); @@ -27,16 +30,20 @@ struct hostnet_tcp_listener { struct _hostnet_tcp_conn active_conn; END_PRIVATE(LIBHW_HOST_NET_H); }; +LO_IMPLEMENTATION_H(io_closer, struct hostnet_tcp_listener, hostnet_tcplist); LO_IMPLEMENTATION_H(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist); void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port); +/* UDP connection *************************************************************/ + struct hostnet_udp_conn { BEGIN_PRIVATE(LIBHW_HOST_NET_H); int fd; uint64_t read_deadline_ns; END_PRIVATE(LIBHW_HOST_NET_H); }; +LO_IMPLEMENTATION_H(io_closer, struct hostnet_udp_conn, hostnet_udp); LO_IMPLEMENTATION_H(net_packet_conn, struct hostnet_udp_conn, hostnet_udp); void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port); diff --git a/libhw_cr/host_net.c b/libhw_cr/host_net.c index 6ed6e46..71a5c37 100644 --- a/libhw_cr/host_net.c +++ b/libhw_cr/host_net.c @@ -7,11 +7,13 @@ #define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */ /* misc */ #include <errno.h> /* for errno, EAGAIN, EINVAL */ +#define error __error #include <error.h> /* for error(3gnu) */ +#undef error #include <stdlib.h> /* for abs(), shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */ -#include <unistd.h> /* for read(), write() */ +#include <sys/uio.h> /* for readv(), writev(), struct iovec */ /* net */ -#include <arpa/inet.h> /* for htons(3p) */ +#include <arpa/inet.h> /* for htons() */ #include <netinet/in.h> /* for struct sockaddr_in */ #include <sys/socket.h> /* for struct sockaddr{,_storage}, SOCK_*, SOL_*, SO_*, socket(), setsockopt(), bind(), listen(), accept() */ /* async */ @@ -19,9 +21,10 @@ #include <signal.h> /* for siginfo_t, struct sigaction, enum sigval, sigaction(), SA_SIGINFO */ #include <libcr/coroutine.h> +#include <libmisc/alloc.h> #include <libmisc/assert.h> #include <libmisc/macro.h> -#include <libobj/obj.h> +#include <libmisc/obj.h> #include <libhw/generic/alarmclock.h> @@ -30,18 +33,17 @@ #include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_us_time() */ -LO_IMPLEMENTATION_C(io_closer, struct hostnet_tcp_listener, hostnet_tcplist, static); -LO_IMPLEMENTATION_C(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist, static); +LO_IMPLEMENTATION_C(io_closer, struct hostnet_tcp_listener, hostnet_tcplist); +LO_IMPLEMENTATION_C(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist); -LO_IMPLEMENTATION_C(io_reader, struct _hostnet_tcp_conn, hostnet_tcp, static); -LO_IMPLEMENTATION_C(io_writer, struct _hostnet_tcp_conn, hostnet_tcp, static); -LO_IMPLEMENTATION_C(io_readwriter, struct _hostnet_tcp_conn, hostnet_tcp, static); -LO_IMPLEMENTATION_C(io_closer, struct _hostnet_tcp_conn, hostnet_tcp, static); -LO_IMPLEMENTATION_C(io_bidi_closer, struct _hostnet_tcp_conn, hostnet_tcp, static); -LO_IMPLEMENTATION_C(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp, static); +LO_IMPLEMENTATION_STATIC(io_reader, struct _hostnet_tcp_conn, hostnet_tcp); +LO_IMPLEMENTATION_STATIC(io_writer, struct _hostnet_tcp_conn, hostnet_tcp); +LO_IMPLEMENTATION_STATIC(io_closer, struct _hostnet_tcp_conn, hostnet_tcp); +LO_IMPLEMENTATION_STATIC(io_bidi_closer, struct _hostnet_tcp_conn, hostnet_tcp); +LO_IMPLEMENTATION_STATIC(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp); -LO_IMPLEMENTATION_C(io_closer, struct hostnet_udp_conn, hostnet_udp, static); -LO_IMPLEMENTATION_C(net_packet_conn, struct hostnet_udp_conn, hostnet_udp, static); +LO_IMPLEMENTATION_C(io_closer, struct hostnet_udp_conn, hostnet_udp); +LO_IMPLEMENTATION_C(net_packet_conn, struct hostnet_udp_conn, hostnet_udp); /* common *********************************************************************/ @@ -52,7 +54,7 @@ static void hostnet_handle_sig_io(int LM_UNUSED(sig), siginfo_t *info, void *LM_ } static void hostnet_init(void) { - struct sigaction action = {0}; + struct sigaction action = {}; if (hostnet_sig_io) return; @@ -62,12 +64,12 @@ static void hostnet_init(void) { action.sa_flags = SA_SIGINFO; action.sa_sigaction = hostnet_handle_sig_io; if (sigaction(hostnet_sig_io, &action, NULL) < 0) - error(1, errno, "sigaction"); + __error(1, errno, "sigaction"); } #define WAKE_COROUTINE(args) do { \ int r; \ - union sigval val = {0}; \ + union sigval val = {}; \ val.sival_int = (int)((args)->cr_coroutine); \ do { \ r = pthread_sigqueue((args)->cr_thread, \ @@ -77,46 +79,47 @@ static void hostnet_init(void) { } while (r == EAGAIN); \ } while (0) -static inline bool RUN_PTHREAD(void *(*fn)(void *), void *args) { +static inline host_errno_t RUN_PTHREAD(void *(*fn)(void *), void *args) { pthread_t thread; + host_errno_t r; bool saved = cr_save_and_disable_interrupts(); - if (pthread_create(&thread, NULL, fn, args)) - return true; + r = pthread_create(&thread, NULL, fn, args); + if (r) { + cr_restore_interrupts(saved); + return r; + } cr_pause_and_yield(); cr_restore_interrupts(saved); - if (pthread_join(thread, NULL)) - return true; - return false; + return pthread_join(thread, NULL); } enum hostnet_timeout_op { - OP_NONE, + OP_CLOSE, OP_SEND, OP_RECV, }; -static inline ssize_t hostnet_map_negerrno(ssize_t v, enum hostnet_timeout_op op) { - if (v >= 0) - return v; - switch (v) { - case -EHOSTUNREACH: - return -NET_EARP_TIMEOUT; - case -ETIMEDOUT: +static inline error hostnet_error(host_errno_t errnum, enum hostnet_timeout_op op) { + assert(errnum > 0); + switch (errnum) { + case EHOSTUNREACH: + return error_new(E_NET_EARP_TIMEOUT); + case ETIMEDOUT: switch (op) { - case OP_NONE: + case OP_CLOSE: assert_notreached("impossible ETIMEDOUT"); case OP_SEND: - return -NET_EACK_TIMEOUT; + return error_new(E_NET_EACK_TIMEOUT); case OP_RECV: - return -NET_ERECV_TIMEOUT; + return error_new(E_NET_ERECV_TIMEOUT); } assert_notreached("invalid timeout op"); - case -EBADF: - return -NET_ECLOSED; - case -EMSGSIZE: - return -NET_EMSGSIZE; + case EBADF: + return error_new(E_NET_ECLOSED); + case EMSGSIZE: + return error_new(E_POSIX_EMSGSIZE); default: - return -NET_EOTHER; + return errno2lm(errnum); } } @@ -127,7 +130,7 @@ void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) union { struct sockaddr_in in; struct sockaddr gen; - } addr = { 0 }; + } addr = {}; hostnet_init(); @@ -135,15 +138,15 @@ void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) addr.in.sin_port = htons(port); listenerfd = socket(AF_INET, SOCK_STREAM, 0); if (listenerfd < 0) - error(1, errno, "socket"); + __error(1, errno, "socket"); if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) - error(1, errno, "setsockopt(fd=%d, SO_REUSEADDR=1)", listenerfd); + __error(1, errno, "setsockopt(fd=%d, SO_REUSEADDR=1)", listenerfd); if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)) < 0) - error(1, errno, "setsockopt(fd=%d, SO_REUSEPORT=1)", listenerfd); + __error(1, errno, "setsockopt(fd=%d, SO_REUSEPORT=1)", listenerfd); if (bind(listenerfd, &addr.gen, sizeof addr) < 0) - error(1, errno, "bind(fd=%d)", listenerfd); + __error(1, errno, "bind(fd=%d)", listenerfd); if (listen(listenerfd, 0) < 0) - error(1, errno, "listen(fd=%d)", listenerfd); + __error(1, errno, "listen(fd=%d)", listenerfd); self->fd = listenerfd; } @@ -157,44 +160,48 @@ struct hostnet_pthread_accept_args { int listenerfd; int *ret_connfd; + host_errno_t *ret_errno; }; static void *hostnet_pthread_accept(void *_args) { struct hostnet_pthread_accept_args *args = _args; *(args->ret_connfd) = accept(args->listenerfd, NULL, NULL); - if (*(args->ret_connfd) < 0) - *(args->ret_connfd) = -errno; + *(args->ret_errno) = errno; WAKE_COROUTINE(args); return NULL; } -static lo_interface net_stream_conn hostnet_tcplist_accept(struct hostnet_tcp_listener *listener) { +net_stream_conn_or_error hostnet_tcplist_accept(struct hostnet_tcp_listener *listener) { assert(listener); int ret_connfd; + host_errno_t ret_errno; struct hostnet_pthread_accept_args args = { .cr_thread = pthread_self(), .cr_coroutine = cr_getcid(), .listenerfd = listener->fd, .ret_connfd = &ret_connfd, + .ret_errno = &ret_errno, }; - if (RUN_PTHREAD(hostnet_pthread_accept, &args)) - return LO_NULL(net_stream_conn); - + host_errno_t thread_errno = RUN_PTHREAD(hostnet_pthread_accept, &args); + if (thread_errno) + return ERROR_NEW_ERR(net_stream_conn, errno2lm(thread_errno)); if (ret_connfd < 0) - return LO_NULL(net_stream_conn); + return ERROR_NEW_ERR(net_stream_conn, hostnet_error(ret_errno, OP_RECV)); listener->active_conn.fd = ret_connfd; listener->active_conn.read_deadline_ns = 0; - return lo_box_hostnet_tcp_as_net_stream_conn(&listener->active_conn); + return ERROR_NEW_VAL(net_stream_conn, LO_BOX(net_stream_conn, &listener->active_conn)); } /* TCP listener close() *******************************************************/ -static int hostnet_tcplist_close(struct hostnet_tcp_listener *listener) { +error hostnet_tcplist_close(struct hostnet_tcp_listener *listener) { assert(listener); - return hostnet_map_negerrno(shutdown(listener->fd, SHUT_RDWR) ? -errno : 0, OP_NONE); + if (shutdown(listener->fd, SHUT_RDWR)) + return hostnet_error(errno, OP_CLOSE); + return ERROR_NULL; } /* TCP read() *****************************************************************/ @@ -214,56 +221,74 @@ struct hostnet_pthread_readv_args { const struct iovec *iov; int iovcnt; - ssize_t *ret; + size_t *ret_size; + host_errno_t *ret_errno; }; static void *hostnet_pthread_readv(void *_args) { struct hostnet_pthread_readv_args *args = _args; - *(args->ret) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, - &args->timeout, sizeof(args->timeout)); - if (*(args->ret) < 0) + int r_i = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, + &args->timeout, sizeof(args->timeout)); + if (r_i) { + *args->ret_size = 0; + *args->ret_errno = errno; goto end; + } - *(args->ret) = readv(args->connfd, args->iov, args->iovcnt); - if (*(args->ret) < 0) + ssize_t r_ss = readv(args->connfd, args->iov, args->iovcnt); + if (r_ss < 0) { + *args->ret_size = 0; + *args->ret_errno = errno; goto end; + } + *args->ret_size = r_ss; + *args->ret_errno = 0; end: - if (*(args->ret) < 0) - *(args->ret) = hostnet_map_negerrno(-errno, OP_SEND); WAKE_COROUTINE(args); return NULL; } -static ssize_t hostnet_tcp_readv(struct _hostnet_tcp_conn *conn, const struct iovec *iov, int iovcnt) { +static size_t_or_error hostnet_tcp_readv(struct _hostnet_tcp_conn *conn, const struct rd_iovec *iov, int iovcnt) { assert(conn); assert(iov); assert(iovcnt > 0); + size_t count = 0; + for (int i = 0; i < iovcnt; i++) + count += iov[i].iov_len; + assert(count); - ssize_t ret; + size_t ret_size; + host_errno_t ret_errno; struct hostnet_pthread_readv_args args = { .cr_thread = pthread_self(), .cr_coroutine = cr_getcid(), .connfd = conn->fd, - .iov = iov, + .iov = (const struct iovec *)iov, .iovcnt = iovcnt, - .ret = &ret, + .ret_size = &ret_size, + .ret_errno = &ret_errno, }; if (conn->read_deadline_ns) { uint64_t now_ns = LO_CALL(bootclock, get_time_ns); if (conn->read_deadline_ns < now_ns) - return -NET_ERECV_TIMEOUT; + return ERROR_NEW_ERR(size_t, error_new(E_NET_ERECV_TIMEOUT)); args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns); } else { - args.timeout = (host_us_time_t){0}; + args.timeout = (host_us_time_t){}; } - if (RUN_PTHREAD(hostnet_pthread_readv, &args)) - return -NET_ETHREAD; - return ret; + host_errno_t thread_errno = RUN_PTHREAD(hostnet_pthread_readv, &args); + if (thread_errno) + return ERROR_NEW_ERR(size_t, errno2lm(thread_errno)); + if (ret_errno) + return ERROR_NEW_ERR(size_t, hostnet_error(ret_errno, OP_RECV)); + if (!ret_size) + return ERROR_NEW_ERR(size_t, error_new(E_EOF)); + return ERROR_NEW_VAL(size_t, ret_size); } /* TCP write() ****************************************************************/ @@ -276,14 +301,15 @@ struct hostnet_pthread_writev_args { const struct iovec *iov; int iovcnt; - ssize_t *ret; + size_t *ret_size; + host_errno_t *ret_errno; }; static void *hostnet_pthread_writev(void *_args) { struct hostnet_pthread_writev_args *args = _args; size_t count = 0; - struct iovec *iov = alloca(sizeof(struct iovec)*args->iovcnt); + struct iovec *iov = stack_alloc(args->iovcnt, struct iovec); for (int i = 0; i < args->iovcnt; i++) { iov[i] = args->iov[i]; count += args->iov[i].iov_len; @@ -294,7 +320,7 @@ static void *hostnet_pthread_writev(void *_args) { while (done < count) { ssize_t r = writev(args->connfd, iov, iovcnt); if (r < 0) { - hostnet_map_negerrno(-errno, OP_RECV); + *args->ret_errno = errno; break; } done += r; @@ -309,45 +335,59 @@ static void *hostnet_pthread_writev(void *_args) { } } if (done == count) - *(args->ret) = done; + *args->ret_errno = 0; + *args->ret_size = done; WAKE_COROUTINE(args); return NULL; } -static ssize_t hostnet_tcp_writev(struct _hostnet_tcp_conn *conn, const struct iovec *iov, int iovcnt) { +static size_t_and_error hostnet_tcp_writev(struct _hostnet_tcp_conn *conn, const struct wr_iovec *iov, int iovcnt) { assert(conn); assert(iov); assert(iovcnt > 0); + size_t count = 0; + for (int i = 0; i < iovcnt; i++) + count += iov[i].iov_len; + assert(count); - ssize_t ret; + size_t ret_size; + host_errno_t ret_errno; struct hostnet_pthread_writev_args args = { .cr_thread = pthread_self(), .cr_coroutine = cr_getcid(), .connfd = conn->fd, - .iov = iov, + .iov = (const struct iovec *)iov, .iovcnt = iovcnt, - .ret = &ret, + .ret_size = &ret_size, + .ret_errno = &ret_errno, }; - if (RUN_PTHREAD(hostnet_pthread_writev, &args)) - return -NET_ETHREAD; - return ret; + int thread_errno = RUN_PTHREAD(hostnet_pthread_writev, &args); + if (thread_errno) + return ERROR_AND(size_t, 0, errno2lm(thread_errno)); + return ERROR_AND(size_t, ret_size, ret_errno ? hostnet_error(ret_errno, OP_SEND) : ERROR_NULL); } /* TCP close() ****************************************************************/ -static int hostnet_tcp_close(struct _hostnet_tcp_conn *conn) { +static error hostnet_tcp_close(struct _hostnet_tcp_conn *conn) { assert(conn); - return hostnet_map_negerrno(shutdown(conn->fd, SHUT_RDWR) ? -errno : 0, OP_NONE); + if (shutdown(conn->fd, SHUT_RDWR)) + return hostnet_error(errno, OP_CLOSE); + return ERROR_NULL; } -static int hostnet_tcp_close_read(struct _hostnet_tcp_conn *conn) { +static error hostnet_tcp_close_read(struct _hostnet_tcp_conn *conn) { assert(conn); - return hostnet_map_negerrno(shutdown(conn->fd, SHUT_RD) ? -errno : 0, OP_NONE); + if (shutdown(conn->fd, SHUT_RD)) + return hostnet_error(errno, OP_CLOSE); + return ERROR_NULL; } -static int hostnet_tcp_close_write(struct _hostnet_tcp_conn *conn) { +static error hostnet_tcp_close_write(struct _hostnet_tcp_conn *conn) { assert(conn); - return hostnet_map_negerrno(shutdown(conn->fd, SHUT_WR) ? -errno : 0, OP_NONE); + if (shutdown(conn->fd, SHUT_WR)) + return hostnet_error(errno, OP_CLOSE); + return ERROR_NULL; } /* UDP init() *****************************************************************/ @@ -358,7 +398,7 @@ void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) { struct sockaddr_in in; struct sockaddr gen; struct sockaddr_storage stor; - } addr = { 0 }; + } addr = {}; hostnet_init(); @@ -366,9 +406,11 @@ void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) { addr.in.sin_port = htons(port); fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) - error(1, errno, "socket"); + __error(1, errno, "socket"); + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &(int){1}, sizeof(int)) < 0) + __error(1, errno, "setsockopt(fd=%d, SO_BROADCAST=1)", fd); if (bind(fd, &addr.gen, sizeof addr) < 0) - error(1, errno, "bind"); + __error(1, errno, "bind"); self->fd = fd; self->read_deadline_ns = 0; @@ -386,7 +428,8 @@ struct hostnet_pthread_sendto_args { struct net_ip4_addr node; uint16_t port; - ssize_t *ret; + size_t *ret_size; + host_errno_t *ret_errno; }; static void *hostnet_pthread_sendto(void *_args) { @@ -395,27 +438,33 @@ static void *hostnet_pthread_sendto(void *_args) { struct sockaddr_in in; struct sockaddr gen; struct sockaddr_storage stor; - } addr = { 0 }; + } addr = {}; addr.in.sin_family = AF_INET; addr.in.sin_addr.s_addr = - (((uint32_t)args->node.octets[0])<<24) | - (((uint32_t)args->node.octets[1])<<16) | - (((uint32_t)args->node.octets[2])<< 8) | - (((uint32_t)args->node.octets[3])<< 0) ; + (((uint32_t)args->node.octets[3])<<24) | + (((uint32_t)args->node.octets[2])<<16) | + (((uint32_t)args->node.octets[1])<< 8) | + (((uint32_t)args->node.octets[0])<< 0) ; addr.in.sin_port = htons(args->port); - *(args->ret) = sendto(args->connfd, args->buf, args->count, 0, &addr.gen, sizeof(addr)); - if (*(args->ret) < 0) - *(args->ret) = hostnet_map_negerrno(-errno, OP_SEND); + ssize_t r = sendto(args->connfd, args->buf, args->count, 0, &addr.gen, sizeof(addr)); + if (r < 0) { + *args->ret_size = 0; + *args->ret_errno = errno; + } else { + *args->ret_size = r; + *args->ret_errno = 0; + } WAKE_COROUTINE(args); return NULL; } -static ssize_t hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t count, - struct net_ip4_addr node, uint16_t port) { +error hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t count, + struct net_ip4_addr node, uint16_t port) { assert(conn); - ssize_t ret; + size_t ret_size; + host_errno_t ret_errno; struct hostnet_pthread_sendto_args args = { .cr_thread = pthread_self(), .cr_coroutine = cr_getcid(), @@ -426,17 +475,21 @@ static ssize_t hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size .node = node, .port = port, - .ret = &ret, + .ret_size = &ret_size, + .ret_errno = &ret_errno, }; - if (RUN_PTHREAD(hostnet_pthread_sendto, &args)) - return -NET_ETHREAD; - return ret; + int thread_errno = RUN_PTHREAD(hostnet_pthread_sendto, &args); + if (thread_errno) + return errno2lm(thread_errno); + if (ret_errno) + return hostnet_error(ret_errno, OP_SEND); + return ERROR_NULL; } /* UDP recvfrom() *************************************************************/ -static void hostnet_udp_set_recv_deadline(struct hostnet_udp_conn *conn, - uint64_t ts_ns) { +void hostnet_udp_set_recv_deadline(struct hostnet_udp_conn *conn, + uint64_t ts_ns) { assert(conn); conn->read_deadline_ns = ts_ns; @@ -451,7 +504,8 @@ struct hostnet_pthread_recvfrom_args { void *buf; size_t count; - ssize_t *ret_size; + size_t *ret_size; + host_errno_t *ret_errno; struct net_ip4_addr *ret_node; uint16_t *ret_port; }; @@ -463,42 +517,49 @@ static void *hostnet_pthread_recvfrom(void *_args) { struct sockaddr_in in; struct sockaddr gen; struct sockaddr_storage stor; - } addr = { 0 }; - socklen_t addr_size; - - *(args->ret_size) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, - &args->timeout, sizeof(args->timeout)); - if (*(args->ret_size) < 0) + } addr = {}; + socklen_t addr_size = sizeof(addr); + + int r_i = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, + &args->timeout, sizeof(args->timeout)); + if (r_i) { + *args->ret_size = 0; + *args->ret_errno = errno; goto end; + } - *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, - MSG_TRUNC, &addr.gen, &addr_size); - if (*(args->ret_size) < 0) + ssize_t r_ss = recvfrom(args->connfd, args->buf, args->count, + MSG_TRUNC, &addr.gen, &addr_size); + if (r_ss < 0) { + *args->ret_size = 0; + *args->ret_errno = errno; goto end; + } + *args->ret_size = r_ss; + *args->ret_errno = 0; assert(addr.in.sin_family == AF_INET); if (args->ret_node) { - args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; - args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; - args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; - args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; + args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; + args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; + args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; + args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; } if (args->ret_port) { (*args->ret_port) = ntohs(addr.in.sin_port); } end: - if (*(args->ret_size) < 0) - *(args->ret_size) = hostnet_map_negerrno(-errno, OP_RECV); WAKE_COROUTINE(args); return NULL; } -static ssize_t hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, size_t count, - struct net_ip4_addr *ret_node, uint16_t *ret_port) { +size_t_or_error hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, size_t count, + struct net_ip4_addr *ret_node, uint16_t *ret_port) { assert(conn); - ssize_t ret; + size_t ret_size; + host_errno_t ret_errno; struct hostnet_pthread_recvfrom_args args = { .cr_thread = pthread_self(), .cr_coroutine = cr_getcid(), @@ -507,28 +568,34 @@ static ssize_t hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, si .buf = buf, .count = count, - .ret_size = &ret, + .ret_size = &ret_size, + .ret_errno = &ret_errno, .ret_node = ret_node, .ret_port = ret_port, }; if (conn->read_deadline_ns) { uint64_t now_ns = LO_CALL(bootclock, get_time_ns); if (conn->read_deadline_ns < now_ns) - return -NET_ERECV_TIMEOUT; + return ERROR_NEW_ERR(size_t, error_new(E_NET_ERECV_TIMEOUT)); args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns); } else { - args.timeout = (host_us_time_t){0}; + args.timeout = (host_us_time_t){}; } - if (RUN_PTHREAD(hostnet_pthread_recvfrom, &args)) - return -NET_ETHREAD; - return ret; + host_errno_t thread_errno = RUN_PTHREAD(hostnet_pthread_recvfrom, &args); + if (thread_errno) + return ERROR_NEW_ERR(size_t, errno2lm(thread_errno)); + if (ret_errno) + return ERROR_NEW_ERR(size_t, hostnet_error(ret_errno, OP_RECV)); + return ERROR_NEW_VAL(size_t, ret_size); } /* UDP close() ****************************************************************/ -static int hostnet_udp_close(struct hostnet_udp_conn *conn) { +error hostnet_udp_close(struct hostnet_udp_conn *conn) { assert(conn); - return hostnet_map_negerrno(close(conn->fd) ? -errno : 0, OP_NONE); + if (close(conn->fd)) + return hostnet_error(errno, OP_CLOSE); + return ERROR_NULL; } diff --git a/libhw_cr/host_util.c b/libhw_cr/host_util.c index 7b3200c..2d45490 100644 --- a/libhw_cr/host_util.c +++ b/libhw_cr/host_util.c @@ -4,9 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <error.h> /* for error(3gnu) */ +#include <errno.h> /* for E* */ #include <signal.h> /* for SIGRTMIN, SIGRTMAX */ +#define error __error +#include <error.h> +#undef error + +#include <libhw/generic/alarmclock.h> /* for {X}S_PER_S */ + #include "host_util.h" int host_sigrt_alloc(void) { @@ -16,6 +22,124 @@ int host_sigrt_alloc(void) { next = SIGRTMIN; int ret = next++; if (ret > SIGRTMAX) - error(1, 0, "SIGRTMAX exceeded"); + __error(1, 0, "SIGRTMAX exceeded"); + return ret; +} + +host_us_time_t ns_to_host_us_time(uint64_t time_ns) { + host_us_time_t ret; + ret.tv_sec = time_ns + /NS_PER_S; + ret.tv_usec = (time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S) + /(NS_PER_S/US_PER_S); + return ret; +} + +host_ns_time_t ns_to_host_ns_time(uint64_t time_ns) { + host_ns_time_t ret; + ret.tv_sec = time_ns + /NS_PER_S; + ret.tv_nsec = time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S; return ret; } + +uint64_t ns_from_host_us_time(host_us_time_t host_time) { + return (((uint64_t)host_time.tv_sec) * NS_PER_S) + + ((uint64_t)host_time.tv_usec * (NS_PER_S/US_PER_S)); +} + +uint64_t ns_from_host_ns_time(host_ns_time_t host_time) { + return (((uint64_t)host_time.tv_sec) * NS_PER_S) + + ((uint64_t)host_time.tv_nsec); +} + +_errnum errno_host2lm(host_errno_t host) { + switch (host) { + case E2BIG: return E_POSIX_E2BIG; + case EACCES: return E_POSIX_EACCES; + case EADDRINUSE: return E_POSIX_EADDRINUSE; + case EADDRNOTAVAIL: return E_POSIX_EADDRNOTAVAIL; + case EAFNOSUPPORT: return E_POSIX_EAFNOSUPPORT; + case EAGAIN: return E_POSIX_EAGAIN; + case EALREADY: return E_POSIX_EALREADY; + case EBADF: return E_POSIX_EBADF; + case EBADMSG: return E_POSIX_EBADMSG; + case EBUSY: return E_POSIX_EBUSY; + case ECANCELED: return E_POSIX_ECANCELED; + case ECHILD: return E_POSIX_ECHILD; + case ECONNABORTED: return E_POSIX_ECONNABORTED; + case ECONNREFUSED: return E_POSIX_ECONNREFUSED; + case ECONNRESET: return E_POSIX_ECONNRESET; + case EDEADLK: return E_POSIX_EDEADLK; + case EDESTADDRREQ: return E_POSIX_EDESTADDRREQ; + case EDOM: return E_POSIX_EDOM; + case EDQUOT: return E_POSIX_EDQUOT; + case EEXIST: return E_POSIX_EEXIST; + case EFAULT: return E_POSIX_EFAULT; + case EFBIG: return E_POSIX_EFBIG; + case EHOSTUNREACH: return E_POSIX_EHOSTUNREACH; + case EIDRM: return E_POSIX_EIDRM; + case EILSEQ: return E_POSIX_EILSEQ; + case EINPROGRESS: return E_POSIX_EINPROGRESS; + case EINTR: return E_POSIX_EINTR; + case EINVAL: return E_POSIX_EINVAL; + case EIO: return E_POSIX_EIO; + case EISCONN: return E_POSIX_EISCONN; + case EISDIR: return E_POSIX_EISDIR; + case ELOOP: return E_POSIX_ELOOP; + case EMFILE: return E_POSIX_EMFILE; + case EMLINK: return E_POSIX_EMLINK; + case EMSGSIZE: return E_POSIX_EMSGSIZE; + case EMULTIHOP: return E_POSIX_EMULTIHOP; + case ENAMETOOLONG: return E_POSIX_ENAMETOOLONG; + case ENETDOWN: return E_POSIX_ENETDOWN; + case ENETRESET: return E_POSIX_ENETRESET; + case ENETUNREACH: return E_POSIX_ENETUNREACH; + case ENFILE: return E_POSIX_ENFILE; + case ENOBUFS: return E_POSIX_ENOBUFS; + case ENODEV: return E_POSIX_ENODEV; + case ENOENT: return E_POSIX_ENOENT; + case ENOEXEC: return E_POSIX_ENOEXEC; + case ENOLCK: return E_POSIX_ENOLCK; + case ENOLINK: return E_POSIX_ENOLINK; + case ENOMEM: return E_POSIX_ENOMEM; + case ENOMSG: return E_POSIX_ENOMSG; + case ENOPROTOOPT: return E_POSIX_ENOPROTOOPT; + case ENOSPC: return E_POSIX_ENOSPC; + case ENOSYS: return E_POSIX_ENOSYS; + case ENOTCONN: return E_POSIX_ENOTCONN; + case ENOTDIR: return E_POSIX_ENOTDIR; + case ENOTEMPTY: return E_POSIX_ENOTEMPTY; + case ENOTRECOVERABLE: return E_POSIX_ENOTRECOVERABLE; + case ENOTSOCK: return E_POSIX_ENOTSOCK; + case ENOTSUP: return E_POSIX_ENOTSUP; + case ENOTTY: return E_POSIX_ENOTTY; + case ENXIO: return E_POSIX_ENXIO; + case EOVERFLOW: return E_POSIX_EOVERFLOW; + case EOWNERDEAD: return E_POSIX_EOWNERDEAD; + case EPERM: return E_POSIX_EPERM; + case EPIPE: return E_POSIX_EPIPE; + case EPROTO: return E_POSIX_EPROTO; + case EPROTONOSUPPORT: return E_POSIX_EPROTONOSUPPORT; + case EPROTOTYPE: return E_POSIX_EPROTOTYPE; + case ERANGE: return E_POSIX_ERANGE; + case EROFS: return E_POSIX_EROFS; + case ESOCKTNOSUPPORT: return E_POSIX_ESOCKTNOSUPPORT; + case ESPIPE: return E_POSIX_ESPIPE; + case ESRCH: return E_POSIX_ESRCH; + case ESTALE: return E_POSIX_ESTALE; + case ETIMEDOUT: return E_POSIX_ETIMEDOUT; + case ETXTBSY: return E_POSIX_ETXTBSY; + case EXDEV: return E_POSIX_EXDEV; + default: + switch (host) { + case EOPNOTSUPP: return E_POSIX_EOPNOTSUPP; + case EWOULDBLOCK: return E_POSIX_EWOULDBLOCK; + default: return E_EUNKNOWN; + } + } +} + +error errno2lm(host_errno_t host) { + return error_new(errno_host2lm(host)); +} diff --git a/libhw_cr/host_util.h b/libhw_cr/host_util.h index 02c04dc..7e559ef 100644 --- a/libhw_cr/host_util.h +++ b/libhw_cr/host_util.h @@ -7,41 +7,24 @@ #ifndef _LIBHW_CR_HOST_UTIL_H_ #define _LIBHW_CR_HOST_UTIL_H_ -#include <time.h> /* for struct timespec */ -#include <sys/time.h> /* for struct timeval */ +#include <stdint.h> /* for uint{n}_t */ +#include <sys/time.h> /* for struct timeval */ +#include <time.h> /* for struct timespec */ -#include <libhw/generic/alarmclock.h> /* for {X}S_PER_S */ +#include <libmisc/error.h> /* for _errnum, error */ int host_sigrt_alloc(void); typedef struct timeval host_us_time_t; typedef struct timespec host_ns_time_t; -static inline host_us_time_t ns_to_host_us_time(uint64_t time_ns) { - host_us_time_t ret; - ret.tv_sec = time_ns - /NS_PER_S; - ret.tv_usec = (time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S) - /(NS_PER_S/US_PER_S); - return ret; -} - -static inline host_ns_time_t ns_to_host_ns_time(uint64_t time_ns) { - host_ns_time_t ret; - ret.tv_sec = time_ns - /NS_PER_S; - ret.tv_nsec = time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S; - return ret; -} - -static inline uint64_t ns_from_host_us_time(host_us_time_t host_time) { - return (((uint64_t)host_time.tv_sec) * NS_PER_S) + - ((uint64_t)host_time.tv_usec * (NS_PER_S/US_PER_S)); -} - -static inline uint64_t ns_from_host_ns_time(host_ns_time_t host_time) { - return (((uint64_t)host_time.tv_sec) * NS_PER_S) + - ((uint64_t)host_time.tv_nsec); -} +host_us_time_t ns_to_host_us_time(uint64_t time_ns); +host_ns_time_t ns_to_host_ns_time(uint64_t time_ns); +uint64_t ns_from_host_us_time(host_us_time_t host_time); +uint64_t ns_from_host_ns_time(host_ns_time_t host_time); + +#define host_errno_t int +_errnum errno_host2lm(host_errno_t host); +error errno2lm(host_errno_t host); #endif /* _LIBHW_CR_HOST_UTIL_H_ */ diff --git a/libhw_cr/rp2040_dma.c b/libhw_cr/rp2040_dma.c index 69116bf..7b78535 100644 --- a/libhw_cr/rp2040_dma.c +++ b/libhw_cr/rp2040_dma.c @@ -1,22 +1,120 @@ /* libhw_cr/rp2040_dma.c - Utilities for sharing the DMA IRQs * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * SPDX-License-Identifier: BSD-3-Clause + * * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <stdbool.h> - #include <hardware/irq.h> /* for irq_set_exclusive_handler() */ +#include <libmisc/log.h> + #include "rp2040_dma.h" +/* static_assert("rp2040_dma.h" == <hardware/irq.h>); */ +static_assert((uint)DMAIRQ_0 == (uint)DMA_IRQ_0); +static_assert((uint)DMAIRQ_1 == (uint)DMA_IRQ_1); + +/* Borrowed from <hardware/dma.h> *********************************************/ + +dma_channel_hw_t *dma_channel_hw_addr(uint channel) { + assert(channel < NUM_DMA_CHANNELS); + return &dma_hw->ch[channel]; +} + +/* Our own code ***************************************************************/ + +typedef uint8_t addr_flag_t; +#define ADDR_FLAG_UNMAPPED ((addr_flag_t)(1<<0)) +#define ADDR_FLAG_UNSAFE ((addr_flag_t)(1<<1)) +#define ADDR_FLAG_RD_OK ((addr_flag_t)(1<<2)) +#define ADDR_FLAG_WR_OK ((addr_flag_t)(1<<3)) +#define ADDR_FLAG_NEEDS_DREQ ((addr_flag_t)(1<<4)) + +static addr_flag_t dma_classify_addr(volatile const void *_addr) { + uintptr_t addr = (uintptr_t)_addr; + switch (addr >> 28) { + case 0x0: /* ROM */ + if (addr < 0x4000) + return ADDR_FLAG_RD_OK; + return ADDR_FLAG_UNMAPPED; + case 0x1: /* XIP */ + switch ((addr >> 24)&0xf) { + case 0x0: case 0x1: case 0x2: case 0x3: /* not safe for DMA */ + return ADDR_FLAG_UNSAFE; + case 0x4: /* CTRL registers */ + if (addr < 0x14000020) + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + return ADDR_FLAG_UNMAPPED; + case 0x5: /* SRAM */ + if (addr < 0x15004000) + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + return ADDR_FLAG_UNMAPPED; + case 0x8: /* SSI registers */ + if (addr < 0x18000064) + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + if (0x180000f0 <= addr && addr < 0x180000fc) + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + return ADDR_FLAG_UNMAPPED; + } + return ADDR_FLAG_UNMAPPED; + case 0x2: /* SRAM */ + if ((addr & 0xfeffffff) < 0x20040000) /* banks 0-3 striped/unstriped depending on bit */ + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + if (addr < 0x20042000) /* banks 4-5 */ + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + return ADDR_FLAG_UNMAPPED; + case 0x4: /* APB Peripherals */ + /* TODO */ + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + case 0x5: /* AHB-Lite Peripherals */ + /* TODO */ + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + case 0xd: /* IOPORT Registers */ + /* TODO */ + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + case 0xe: /* Cortex-M0+ internal registers */ + /* TODO */ + return ADDR_FLAG_RD_OK | ADDR_FLAG_WR_OK; + } + return ADDR_FLAG_UNMAPPED; +} + +bool dma_is_unsafe(volatile const void *addr) { + return dma_classify_addr(addr) & ADDR_FLAG_UNSAFE; +} + +#ifndef NDEBUG +void dma_assert_addrs(volatile void *dst, volatile const void *src) { + addr_flag_t dst_flags = dma_classify_addr(dst); + addr_flag_t src_flags = dma_classify_addr(src); + bool bad = false; + if (!(dst_flags & ADDR_FLAG_WR_OK)) { + log_n_errorln(ASSERT, "dma_assert_addrs(", (ptr, dst), ", ", (ptr, src), "): invalid destination"); + bad = true; + } + if (!(src_flags & ADDR_FLAG_RD_OK)) { + log_n_errorln(ASSERT, "dma_assert_addrs(", (ptr, dst), ", ", (ptr, src), "): invalid source"); + bad = true; + } + if (!bad && (dst_flags & ADDR_FLAG_NEEDS_DREQ && src_flags & ADDR_FLAG_NEEDS_DREQ) ) { + log_n_errorln(ASSERT, "dma_assert_addrs(", (ptr, dst), ", ", (ptr, src), "): source and destination both required DREQs"); + bad = true; + } + if (bad) + __lm_abort(); +} +#endif + struct dmairq_handler_entry { dmairq_handler_t fn; void *arg; }; -struct dmairq_handler_entry dmairq_handlers[NUM_DMA_CHANNELS] = {0}; +struct dmairq_handler_entry dmairq_handlers[NUM_DMA_CHANNELS] = {}; -bool dmairq_initialized[NUM_DMA_IRQS] = {0}; +bool dmairq_initialized[NUM_DMA_IRQS] = {}; static void dmairq_handler(void) { enum dmairq irq = __get_current_exception() - VTABLE_FIRST_IRQ; @@ -24,6 +122,7 @@ static void dmairq_handler(void) { assert(irq_idx < NUM_DMA_IRQS); uint32_t regval = dma_hw->irq_ctrl[irq_idx].ints; + dma_hw->intr = regval; /* acknowledge irq */ for (uint channel = 0; channel < NUM_DMA_CHANNELS; channel++) { if (regval & 1u<<channel) { struct dmairq_handler_entry *handler = &dmairq_handlers[channel]; @@ -31,8 +130,6 @@ static void dmairq_handler(void) { handler->fn(handler->arg, irq, channel); } } - /* acknowledge irq */ - dma_hw->intr = regval; } void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg) { diff --git a/libhw_cr/rp2040_dma.h b/libhw_cr/rp2040_dma.h index c7f5a8f..1392e1f 100644 --- a/libhw_cr/rp2040_dma.h +++ b/libhw_cr/rp2040_dma.h @@ -10,33 +10,20 @@ #ifndef _LIBHW_CR_RP2040_DMA_H_ #define _LIBHW_CR_RP2040_DMA_H_ -#include <assert.h> #include <stddef.h> /* for offsetof() */ #include <stdint.h> /* for uint32_t */ #include <hardware/regs/dreq.h> /* for DREQ_* for use with DMA_CTRL_TREQ_SEL() */ -#include <hardware/structs/dma.h> /* for dma_hw, dma_channel_hw_t, DMA_NUM_CHANNELS */ +#include <hardware/structs/dma.h> /* for dma_hw, DMA_NUM_CHANNELS */ +#include <libmisc/assert.h> #include <libmisc/macro.h> /* for LM_FLOORLOG2() */ -/* Borrowed from <hardware/dma.h> *********************************************/ - -static inline dma_channel_hw_t *dma_channel_hw_addr(uint channel) { - assert(channel < NUM_DMA_CHANNELS); - return &dma_hw->ch[channel]; -} - -enum dma_channel_transfer_size { - DMA_SIZE_8 = 0, ///< Byte transfer (8 bits) - DMA_SIZE_16 = 1, ///< Half word transfer (16 bits) - DMA_SIZE_32 = 2 ///< Word transfer (32 bits) -}; - -/* Our own code ***************************************************************/ +/* IRQ ************************************************************************/ enum dmairq { - DMAIRQ_0 = DMA_IRQ_0, - DMAIRQ_1 = DMA_IRQ_1, + DMAIRQ_0 = 11, + DMAIRQ_1 = 12, }; typedef void (*dmairq_handler_t)(void *arg, enum dmairq irq, uint channel); @@ -46,61 +33,64 @@ typedef void (*dmairq_handler_t)(void *arg, enum dmairq irq, uint channel); * has a NULL trigger (depending on the channel's configuration). * * Your handler does not need to acknowledge the IRQ; that will be - * done for you after your handler is called. + * done for you before your handler is called. * * It is illegal to enable the same channel on more than one IRQ. */ void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg); -#define DMA_CTRL_ENABLE (1<<0) -#define DMA_CTRL_HI_PRIO (1<<1) -#define DMA_CTRL_DATA_SIZE(sz) ((sz)<<2) -#define DMA_CTRL_INCR_READ (1<<4) -#define DMA_CTRL_INCR_WRITE (1<<5) -#define _DMA_CTRL_RING_BITS(b) ((b)<<6) -#define _DMA_CTRL_RING_RD (0) -#define _DMA_CTRL_RING_WR (1<<10) -#define DMA_CTRL_RING(rdwr, bits) (_DMA_CTRL_RING_##rdwr | _DMA_CTRL_RING_BITS(bits)) -#define DMA_CTRL_CHAIN_TO(ch) ((ch)<<11) -#define DMA_CTRL_TREQ_SEL(dreq) ((dreq)<<15) -#define DMA_CTRL_IRQ_QUIET (1<<21) -#define DMA_CTRL_BSWAP (1<<22) -#define DMA_CTRL_SNIFF_EN (1<<23) - -/* | elem | val | name */ -#define READ_ADDR /*|*/volatile const void/*|*/ * /*|*/read_addr -#define WRITE_ADDR /*|*/volatile void/*|*/ * /*|*/write_addr -#define TRANS_COUNT /*|*/ /*|*/uint32_t/*|*/trans_count -#define CTRL /*|*/ /*|*/uint32_t/*|*/ctrl - -/* { +0x0 ; +0x4 ; +0x8 ; +0xC (Trigger) */ -struct dma_alias0 { READ_ADDR ; WRITE_ADDR ; TRANS_COUNT ; CTRL ; }; -struct dma_alias1 { CTRL ; READ_ADDR ; WRITE_ADDR ; TRANS_COUNT ; }; -struct dma_alias2 { CTRL ; TRANS_COUNT ; READ_ADDR ; WRITE_ADDR ; }; -struct dma_alias3 { CTRL ; WRITE_ADDR ; TRANS_COUNT ; READ_ADDR ; }; -struct dma_alias0_short2 { TRANS_COUNT ; CTRL ; }; -struct dma_alias1_short2 { WRITE_ADDR ; TRANS_COUNT ; }; -struct dma_alias2_short2 { READ_ADDR ; WRITE_ADDR ; }; -struct dma_alias3_short2 { TRANS_COUNT ; READ_ADDR ; }; -struct dma_alias0_short3 { CTRL ; }; -struct dma_alias1_short3 { TRANS_COUNT ; }; -struct dma_alias2_short3 { WRITE_ADDR ; }; -struct dma_alias3_short3 { READ_ADDR ; }; +/* Control blocks *************************************************************/ + +bool dma_is_unsafe(volatile const void *addr); +#ifdef NDEBUG +#define dma_assert_addrs(dst, src) ((void)0) +#else +void dma_assert_addrs(volatile void *dst, volatile const void *src); +#endif + +/* types =================================================*/ + +/* | elem | val | name */ +#define READ_ADDR /*|*/volatile const void/*|*/ * /*|*/read_addr +#define WRITE_ADDR /*|*/volatile void/*|*/ * /*|*/write_addr +#define XFER_COUNT /*|*/ /*|*/uint32_t/*|*/xfer_count +#define CTRL /*|*/ /*|*/uint32_t/*|*/ctrl + +/* { +0x0 ; +0x4 ; +0x8 ; +0xC (Trigger) */ +struct dma_alias0 { READ_ADDR ; WRITE_ADDR ; XFER_COUNT ; CTRL ; }; +struct dma_alias1 { CTRL ; READ_ADDR ; WRITE_ADDR ; XFER_COUNT ; }; +struct dma_alias2 { CTRL ; XFER_COUNT ; READ_ADDR ; WRITE_ADDR ; }; +struct dma_alias3 { CTRL ; WRITE_ADDR ; XFER_COUNT ; READ_ADDR ; }; +struct dma_alias0_short2 { XFER_COUNT ; CTRL ; }; +struct dma_alias1_short2 { WRITE_ADDR ; XFER_COUNT ; }; +struct dma_alias2_short2 { READ_ADDR ; WRITE_ADDR ; }; +struct dma_alias3_short2 { XFER_COUNT ; READ_ADDR ; }; +struct dma_alias0_short3 { CTRL ; }; +struct dma_alias1_short3 { XFER_COUNT ; }; +struct dma_alias2_short3 { WRITE_ADDR ; }; +struct dma_alias3_short3 { READ_ADDR ; }; #undef CTRL -#undef TRANS_COUNT +#undef XFER_COUNT #undef WRITE_ADDR #undef READ_ADDR +/* locations =============================================*/ + +dma_channel_hw_t *dma_channel_hw_addr(uint channel); + #define DMA_CHAN_ADDR(CH, TYP) ((TYP *volatile)_Generic((TYP){}, \ + \ struct dma_alias0: &dma_channel_hw_addr(CH)->read_addr, \ struct dma_alias1: &dma_channel_hw_addr(CH)->al1_ctrl, \ struct dma_alias2: &dma_channel_hw_addr(CH)->al2_ctrl, \ struct dma_alias3: &dma_channel_hw_addr(CH)->al3_ctrl, \ + \ struct dma_alias0_short2: &dma_channel_hw_addr(CH)->transfer_count, \ struct dma_alias1_short2: &dma_channel_hw_addr(CH)->al1_write_addr, \ struct dma_alias2_short2: &dma_channel_hw_addr(CH)->al2_read_addr, \ struct dma_alias3_short2: &dma_channel_hw_addr(CH)->al3_transfer_count, \ + \ struct dma_alias0_short3: &dma_channel_hw_addr(CH)->ctrl_trig, \ struct dma_alias1_short3: &dma_channel_hw_addr(CH)->al1_transfer_count_trig, \ struct dma_alias2_short3: &dma_channel_hw_addr(CH)->al2_write_addr_trig, \ @@ -108,24 +98,48 @@ struct dma_alias3_short3 { READ_ADDR ; }; #define DMA_IS_TRIGGER(TYP, FIELD) (offsetof(TYP, FIELD) == 0xC) -#define DMA_CHAN_WR_TRANS_COUNT(TYP) \ - (sizeof(TYP)/4) - -#define DMA_CHAN_WR_CTRL(TYP) ( DMA_CTRL_DATA_SIZE(DMA_SIZE_32) \ - | DMA_CTRL_INCR_WRITE \ - | DMA_CTRL_RING(WR, LM_FLOORLOG2(sizeof(TYP))) \ - ) - #define DMA_NONTRIGGER(CH, FIELD) (DMA_CHAN_ADDR(CH, _DMA_NONTRIGGER_##FIELD)->FIELD) #define _DMA_NONTRIGGER_read_addr struct dma_alias0 #define _DMA_NONTRIGGER_write_addr struct dma_alias0 -#define _DMA_NONTRIGGER_trans_count struct dma_alias0 +#define _DMA_NONTRIGGER_xfer_count struct dma_alias0 #define _DMA_NONTRIGGER_ctrl struct dma_alias1 #define DMA_TRIGGER(CH, FIELD) (DMA_CHAN_ADDR(CH, _DMA_TRIGGER_##FIELD)->FIELD) #define _DMA_TRIGGER_read_addr struct dma_alias3 #define _DMA_TRIGGER_write_addr struct dma_alias2 -#define _DMA_TRIGGER_trans_count struct dma_alias1 +#define _DMA_TRIGGER_xfer_count struct dma_alias1 #define _DMA_TRIGGER_ctrl struct dma_alias0 +/* block->ctrl register *******************************************************/ + +enum dma_transfer_size { + DMA_SIZE_8 = 0, /** Byte transfer (8 bits) */ + DMA_SIZE_16 = 1, /** Half word transfer (16 bits) */ + DMA_SIZE_32 = 2, /** Word transfer (32 bits) */ +}; + +#define DMA_CTRL_ENABLE (1<<0) +#define DMA_CTRL_HI_PRIO (1<<1) +#define DMA_CTRL_DATA_SIZE(sz) ((sz)<<2) +#define DMA_CTRL_INCR_READ (1<<4) +#define DMA_CTRL_INCR_WRITE (1<<5) +#define _DMA_CTRL_RING_BITS(b) ((b)<<6) +#define _DMA_CTRL_RING_RD (0) +#define _DMA_CTRL_RING_WR (1<<10) +#define DMA_CTRL_RING(rdwr, bits) (_DMA_CTRL_RING_##rdwr | _DMA_CTRL_RING_BITS(bits)) +#define DMA_CTRL_CHAIN_TO(ch) ((ch)<<11) +#define DMA_CTRL_TREQ_SEL(dreq) ((dreq)<<15) +#define DMA_CTRL_IRQ_QUIET (1<<21) +#define DMA_CTRL_BSWAP (1<<22) +#define DMA_CTRL_SNIFF_EN (1<<23) + +/* Utilities to build control blocks that write to other control blocks *******/ + +#define DMA_CHAN_WR_XFER_COUNT(TYP) \ + (sizeof(TYP)/4) +#define DMA_CHAN_WR_CTRL(TYP) ( DMA_CTRL_DATA_SIZE(DMA_SIZE_32) \ + | DMA_CTRL_INCR_WRITE \ + | DMA_CTRL_RING(WR, LM_FLOORLOG2(sizeof(TYP))) \ + ) + #endif /* _LIBHW_CR_RP2040_DMA_H_ */ diff --git a/libhw_cr/rp2040_gpioirq.c b/libhw_cr/rp2040_gpioirq.c index 1ae74f9..ecbdb04 100644 --- a/libhw_cr/rp2040_gpioirq.c +++ b/libhw_cr/rp2040_gpioirq.c @@ -4,8 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +#include <hardware/irq.h> /* for irq_set_exclusive_handler() */ #include <hardware/structs/io_bank0.h> /* for io_bank0_hw */ -#include <hardware/irq.h> /* for irq_set_exclusive_handler() */ #include <libmisc/macro.h> @@ -15,7 +15,7 @@ struct gpioirq_handler_entry { gpioirq_handler_t fn; void *arg; }; -struct gpioirq_handler_entry gpioirq_handlers[NUM_BANK0_GPIOS][4] = {0}; +struct gpioirq_handler_entry gpioirq_handlers[NUM_BANK0_GPIOS][4] = {}; int gpioirq_core = -1; diff --git a/libhw_cr/rp2040_hwspi.c b/libhw_cr/rp2040_hwspi.c index d181650..f667332 100644 --- a/libhw_cr/rp2040_hwspi.c +++ b/libhw_cr/rp2040_hwspi.c @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <alloca.h> -#include <inttypes.h> /* for PRIu{n} */ - #include <hardware/clocks.h> /* for clock_get_hz() and clk_peri */ #include <hardware/gpio.h> #include <hardware/spi.h> #include <libcr/coroutine.h> +#include <libmisc/alloc.h> #include <libmisc/assert.h> #define LOG_NAME RP2040_SPI @@ -29,23 +27,40 @@ #ifndef CONFIG_RP2040_SPI_DEBUG #error config.h must define CONFIG_RP2040_SPI_DEBUG (bool) #endif +#ifndef CONFIG_RP2040_SPI_MAX_DMABUF + #error config.h must define CONFIG_RP2040_SPI_DEBUG (non-negative integer) +#endif -LO_IMPLEMENTATION_C(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi, static); -LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi, static); +LO_IMPLEMENTATION_C(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi); +LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi); static void rp2040_hwspi_intrhandler(void *_self, enum dmairq LM_UNUSED(irq), uint LM_UNUSED(channel)) { struct rp2040_hwspi *self = _self; gpio_put(self->pin_cs, 1); - cr_sema_signal_from_intrhandler(&self->sema); + assert(((spi_hw_t *)self->inst)->sr == 0b11); + cr_unpause_from_intrhandler(self->waiter); } +#define assert_2distinct(a, b) \ + assert(a != b) + +#define assert_3distinct(a, b, c) \ + assert_2distinct(a, b); \ + assert(c != a); \ + assert(c != b) + #define assert_4distinct(a, b, c, d) \ - assert(a != b); \ - assert(a != c); \ - assert(a != d); \ - assert(b != c); \ - assert(b != d); \ - assert(c != d); + assert_3distinct(a, b, c); \ + assert(d != a); \ + assert(d != b); \ + assert(d != c) + +#define assert_5distinct(a, b, c, d, e) \ + assert_4distinct(a, b, c, d); \ + assert(e != a); \ + assert(e != b); \ + assert(e != c); \ + assert(e != d) void _rp2040_hwspi_init(struct rp2040_hwspi *self, enum rp2040_hwspi_instance inst_num, @@ -70,10 +85,12 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self, assert(self); assert(baudrate_hz); uint32_t clk_peri_hz = clock_get_hz(clk_peri); - debugf("clk_peri = %"PRIu32"Hz", clk_peri_hz); + log_debugln("clk_peri = ", clk_peri_hz, "Hz"); assert(baudrate_hz*2 <= clk_peri_hz); assert_4distinct(pin_miso, pin_mosi, pin_clk, pin_cs); - assert_4distinct(dma1, dma2, dma3, dma4); + /* I don't trust DMA channel 0 + * https://github.com/raspberrypi/pico-feedback/issues/464 */ + assert_5distinct(0, dma1, dma2, dma3, dma4); /* Regarding the constraints on pin assignments: see the * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions". */ @@ -96,8 +113,9 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self, assert_notreached("invalid hwspi instance number"); } + /* Initialize the PL022. */ actual_baudrate_hz = spi_init(inst, baudrate_hz); - debugf("baudrate = %uHz", actual_baudrate_hz); + log_debugln("baudrate = ", actual_baudrate_hz, "Hz"); assert(actual_baudrate_hz == baudrate_hz); spi_set_format(inst, 8, (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0, @@ -130,7 +148,6 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self, self->dma_tx_data = dma3; self->dma_rx_data = dma4; self->dead_until_ns = 0; - self->sema = (cr_sema_t){}; /* Initialize the interrupt handler. */ /* We do this on (just) the rx channel, because the way the @@ -138,7 +155,7 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self, dmairq_set_and_enable_exclusive_handler(DMAIRQ_0, self->dma_rx_data, rp2040_hwspi_intrhandler, self); } -static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct duplex_iovec *iov, int iovcnt) { +size_t_and_error rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct duplex_iovec *iov, int iovcnt) { assert(self); assert(self->inst); assert(iov); @@ -158,29 +175,38 @@ static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct dupl uint8_t bogus_rx_dst; + size_t count = 0; + size_t unsafe_count = 0; int pruned_iovcnt = 0; - for (int i = 0; i < iovcnt; i++) - if (iov[i].iov_len) - pruned_iovcnt++; - if (!pruned_iovcnt) - return; + for (int i = 0; i < iovcnt; i++) { + if (!iov[i].iov_len) + continue; + pruned_iovcnt++; + count += iov[i].iov_len; + if (dma_is_unsafe(iov[i].iov_write_from)) + unsafe_count += iov[i].iov_len; + } + assert(count); + assert(unsafe_count <= CONFIG_RP2040_SPI_MAX_DMABUF); - /* It doesn't *really* matter which aliases we choose: + assert(((spi_hw_t *)self->inst)->sr == 0b11); + + /* The code following this initial declaration is generic to + * the alias, so changing which alias is used is easy. But + * which aliases should we choose? * - * - None of our fields can be NULL (so no - * false-termination). + * Hard requirements: * - * - Moving const fields first so they don't have to be - * re-programmed each time isn't possible for us; there - * need to be at least 2 const fields, and we only have 1 - * (read_addr for rx_data_blocks, and write_addr for - * tx_data_blocks). + * - The RP2040 can read from NULL (that's where the ROM is), + * so we need the tx channel's read_addr to not be the + * trigger, to avoid accidental null-triggers. + * false-termination). * - * The code following this initial declaration is generic to - * the alias, so changing which alias is used is easy. + * Soft requirements: * - * Since we have no hard requirements, here are some mild - * preferences: + * - We can't write to NULL (it's ROM), but let's give the + * same consideration to the rx channel's write_addr + * anyway. * * - I like the aliases being different for each channel, * because it helps prevent alias-specific code from @@ -192,80 +218,108 @@ static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct dupl * cleared before the trigger, and at the end the control * block is clean and zeroed-out. * - * - Conversely, I like the tx channel (the non-interrupt - * channel) having ctrl *not* be the trigger, so that - * DMA_CTRL_IRQ_QUIET is cleared by the time the trigger - * happens, so the IRQ machinery doesn't need to be engaged - * at all. + * Non-requirements: + * + * - Moving const fields first so they don't have to be + * re-programmed each time isn't possible for us; there + * need to be at least 2 const fields, and we only have 1 + * (read_addr for rx_data_blocks, and write_addr for + * tx_data_blocks). */ - struct dma_alias1 *tx_data_blocks = alloca(sizeof(struct dma_alias1)*(pruned_iovcnt+1)); - struct dma_alias0 *rx_data_blocks = alloca(sizeof(struct dma_alias0)*(pruned_iovcnt+1)); - static_assert(!DMA_IS_TRIGGER(typeof(tx_data_blocks[0]), ctrl)); - static_assert(DMA_IS_TRIGGER(typeof(rx_data_blocks[0]), ctrl)); + [[gnu::cleanup(heap_cleanup)]] struct dma_alias1 *tx_data_blocks = heap_alloc(pruned_iovcnt, struct dma_alias1); + [[gnu::cleanup(heap_cleanup)]] struct dma_alias0 *rx_data_blocks = heap_alloc(pruned_iovcnt+1, struct dma_alias0); /* extra +1 block for null trigger */ + /* hard requirements */ + static_assert(!DMA_IS_TRIGGER(typeof(tx_data_blocks[0]), read_addr)); /* avoid accidental null-trigger */ + /* soft requirements */ + static_assert(!DMA_IS_TRIGGER(typeof(rx_data_blocks[0]), write_addr)); /* avoid accidental null-trigger */ + static_assert(!__builtin_types_compatible_p(typeof(tx_data_blocks[0]), typeof(rx_data_blocks[0]))); /* help detect code errors */ + static_assert(DMA_IS_TRIGGER(typeof(rx_data_blocks[0]), ctrl)); /* avoid needing to set IRQ_QUIET in the null-trigger block */ + /* Core data blocks. */ + [[gnu::cleanup(heap_cleanup)]] void *dmabuf = NULL; + if (unsafe_count) + dmabuf = heap_alloc(unsafe_count, char); + size_t dmabuf_pos = 0; for (int i = 0, j = 0; i < iovcnt; i++) { if (!iov[i].iov_len) continue; - tx_data_blocks[j] = (typeof(tx_data_blocks[0])){ - .read_addr = (iov[i].iov_write_from != IOVEC_DISCARD) ? iov[i].iov_write_from : &self->bogus_data, - .write_addr = &spi_get_hw(self->inst)->dr, - .trans_count = iov[i].iov_len, - .ctrl = (DMA_CTRL_ENABLE - | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) - | ((iov[i].iov_write_from != IOVEC_DISCARD) ? DMA_CTRL_INCR_READ : 0) - | DMA_CTRL_CHAIN_TO(self->dma_tx_ctrl) - | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, true)) - | DMA_CTRL_IRQ_QUIET), + + const void *write_from = iov[i].iov_write_from; + if (write_from == IOVEC_DISCARD) + write_from = &self->bogus_data; + else if (dma_is_unsafe(write_from)) { + memcpy(dmabuf+dmabuf_pos, write_from, iov[i].iov_len); + write_from = dmabuf+dmabuf_pos; + dmabuf_pos += iov[i].iov_len; + } + tx_data_blocks[j] = (typeof(tx_data_blocks[0])){ + .read_addr = write_from, + .write_addr = &spi_get_hw(self->inst)->dr, + .xfer_count = iov[i].iov_len, + .ctrl = (DMA_CTRL_ENABLE + | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) + | ((iov[i].iov_write_from != IOVEC_DISCARD) ? DMA_CTRL_INCR_READ : 0) + | ((j+1 < pruned_iovcnt) ? DMA_CTRL_CHAIN_TO(self->dma_tx_ctrl) : 0) + | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, true))), }; - rx_data_blocks[j] = (typeof(rx_data_blocks[0])){ - .read_addr = &spi_get_hw(self->inst)->dr, - .write_addr = (iov[i].iov_read_to != IOVEC_DISCARD) ? iov[i].iov_read_to : &bogus_rx_dst, - .trans_count = iov[i].iov_len, - .ctrl = (DMA_CTRL_ENABLE - | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) - | ((iov[i].iov_read_to != IOVEC_DISCARD) ? DMA_CTRL_INCR_WRITE : 0) - | DMA_CTRL_CHAIN_TO(self->dma_rx_ctrl) - | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, false)) - | DMA_CTRL_IRQ_QUIET), + dma_assert_addrs(tx_data_blocks[j].write_addr, tx_data_blocks[j].read_addr); + + void *read_to = iov[i].iov_read_to; + if (read_to == IOVEC_DISCARD) + read_to = &bogus_rx_dst; + rx_data_blocks[j] = (typeof(rx_data_blocks[0])){ + .read_addr = &spi_get_hw(self->inst)->dr, + .write_addr = read_to, + .xfer_count = iov[i].iov_len, + .ctrl = (DMA_CTRL_ENABLE + | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) + | ((iov[i].iov_read_to != IOVEC_DISCARD) ? DMA_CTRL_INCR_WRITE : 0) + | DMA_CTRL_CHAIN_TO(self->dma_rx_ctrl) + | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, false)) + | DMA_CTRL_IRQ_QUIET), }; + dma_assert_addrs(rx_data_blocks[j].write_addr, rx_data_blocks[j].read_addr); + j++; } - tx_data_blocks[pruned_iovcnt] = (typeof(tx_data_blocks[0])){0}; - rx_data_blocks[pruned_iovcnt] = (typeof(rx_data_blocks[0])){0}; - /* If ctrl isn't the trigger then we need to make sure that - * DMA_CTRL_IRQ_QUIET isn't cleared before the trigger - * happens. */ - if (!DMA_IS_TRIGGER(typeof(rx_data_blocks[0]), ctrl)) - rx_data_blocks[pruned_iovcnt].ctrl = DMA_CTRL_IRQ_QUIET; + /* Null-trigger (generate IRQ). */ + rx_data_blocks[pruned_iovcnt] = (typeof(rx_data_blocks[0])){ + /* If ctrl isn't the trigger then we need to make sure + * that DMA_CTRL_IRQ_QUIET isn't cleared before the + * trigger happens. */ + .ctrl = DMA_IS_TRIGGER(typeof(rx_data_blocks[0]), ctrl) ? 0 : DMA_CTRL_IRQ_QUIET, + }; /* Set up ctrl. */ DMA_NONTRIGGER(self->dma_tx_ctrl, read_addr) = tx_data_blocks; DMA_NONTRIGGER(self->dma_tx_ctrl, write_addr) = DMA_CHAN_ADDR(self->dma_tx_data, typeof(tx_data_blocks[0])); - DMA_NONTRIGGER(self->dma_tx_ctrl, trans_count) = DMA_CHAN_WR_TRANS_COUNT(typeof(tx_data_blocks[0])); + DMA_NONTRIGGER(self->dma_tx_ctrl, xfer_count) = DMA_CHAN_WR_XFER_COUNT(typeof(tx_data_blocks[0])); DMA_NONTRIGGER(self->dma_tx_ctrl, ctrl) = (DMA_CTRL_ENABLE | DMA_CHAN_WR_CTRL(typeof(tx_data_blocks[0])) | DMA_CTRL_INCR_READ - | DMA_CTRL_CHAIN_TO(self->dma_tx_data) - | DMA_CTRL_TREQ_SEL(DREQ_FORCE) - | DMA_CTRL_IRQ_QUIET); + | DMA_CTRL_TREQ_SEL(DREQ_FORCE)); DMA_NONTRIGGER(self->dma_rx_ctrl, read_addr) = rx_data_blocks; DMA_NONTRIGGER(self->dma_rx_ctrl, write_addr) = DMA_CHAN_ADDR(self->dma_rx_data, typeof(rx_data_blocks[0])); - DMA_NONTRIGGER(self->dma_rx_ctrl, trans_count) = DMA_CHAN_WR_TRANS_COUNT(typeof(rx_data_blocks[0])); + DMA_NONTRIGGER(self->dma_rx_ctrl, xfer_count) = DMA_CHAN_WR_XFER_COUNT(typeof(rx_data_blocks[0])); DMA_NONTRIGGER(self->dma_rx_ctrl, ctrl) = (DMA_CTRL_ENABLE | DMA_CHAN_WR_CTRL(typeof(rx_data_blocks[0])) | DMA_CTRL_INCR_READ - | DMA_CTRL_CHAIN_TO(self->dma_rx_data) - | DMA_CTRL_TREQ_SEL(DREQ_FORCE) - | DMA_CTRL_IRQ_QUIET); + | DMA_CTRL_TREQ_SEL(DREQ_FORCE)); /* Run. */ - uint64_t now = LO_CALL(bootclock, get_time_ns); - if (now < self->dead_until_ns) + + self->waiter = cr_getcid(); + + if (LO_CALL(bootclock, get_time_ns) < self->dead_until_ns) sleep_until_ns(self->dead_until_ns); + bool saved = cr_save_and_disable_interrupts(); gpio_put(self->pin_cs, 0); - dma_hw->multi_channel_trigger = (1u<<self->dma_tx_ctrl) | (1u<<self->dma_rx_ctrl); + dma_hw->multi_channel_trigger = (1<<self->dma_tx_ctrl) | (1<<self->dma_rx_ctrl); + cr_pause_and_yield(); + assert(((spi_hw_t *)self->inst)->sr == 0b11); cr_restore_interrupts(saved); - cr_sema_wait(&self->sema); + self->dead_until_ns = LO_CALL(bootclock, get_time_ns) + self->min_delay_ns; + return ERROR_AND(size_t, count, ERROR_NULL); } diff --git a/libhw_cr/rp2040_hwtimer.c b/libhw_cr/rp2040_hwtimer.c index 8227abb..6d7e868 100644 --- a/libhw_cr/rp2040_hwtimer.c +++ b/libhw_cr/rp2040_hwtimer.c @@ -27,8 +27,7 @@ struct rp2040_hwtimer { bool initialized; struct alarmclock_trigger *queue; }; -LO_IMPLEMENTATION_H(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer); -LO_IMPLEMENTATION_C(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer, static); +LO_IMPLEMENTATION_STATIC(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer); /* Globals ********************************************************************/ @@ -44,7 +43,7 @@ static_assert(sizeof(hwtimers)/sizeof(hwtimers[0]) == _RP2040_HWALARM_NUM); lo_interface alarmclock rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num) { assert(alarm_num < _RP2040_HWALARM_NUM); - return lo_box_rp2040_hwtimer_as_alarmclock(&hwtimers[alarm_num]); + return LO_BOX(alarmclock, &hwtimers[alarm_num]); } @@ -52,7 +51,11 @@ static uint64_t rp2040_hwtimer_get_time_ns(struct rp2040_hwtimer *) { return timer_time_us_64(timer_hw) * (NS_PER_S/US_PER_S); } -#define NS_TO_US_ROUNDUP(x) LM_CEILDIV(x, NS_PER_S/US_PER_S) +static uint32_t ns_to_us_roundup_and_cap(uint64_t ns) { + if (ns >= ((uint64_t)(UINT32_MAX))*1000) + return UINT32_MAX; + return (ns+999)/1000; +} static void rp2040_hwtimer_intrhandler(void) { uint irq_num = __get_current_exception() - VTABLE_FIRST_IRQ; @@ -62,7 +65,7 @@ static void rp2040_hwtimer_intrhandler(void) { struct rp2040_hwtimer *alarmclock = &hwtimers[alarm_num]; while (alarmclock->queue && - NS_TO_US_ROUNDUP(alarmclock->queue->fire_at_ns) <= timer_time_us_64(timer_hw)) { + alarmclock->queue->fire_at_ns <= timer_time_us_64(timer_hw)*1000) { struct alarmclock_trigger *trigger = alarmclock->queue; trigger->cb(trigger->cb_arg); alarmclock->queue = trigger->next; @@ -74,10 +77,10 @@ static void rp2040_hwtimer_intrhandler(void) { hw_clear_bits(&timer_hw->intf, 1 << alarm_num); /* Clear "force"ing the interrupt. */ hw_clear_bits(&timer_hw->intr, 1 << alarm_num); /* Clear natural firing of the alarm. */ if (alarmclock->queue) - timer_hw->alarm[alarm_num] = (uint32_t)NS_TO_US_ROUNDUP(alarmclock->queue->fire_at_ns); + timer_hw->alarm[alarm_num] = ns_to_us_roundup_and_cap(alarmclock->queue->fire_at_ns); } -static bool rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock, +static void rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock, struct alarmclock_trigger *trigger, uint64_t fire_at_ns, void (*cb)(void *), @@ -87,18 +90,13 @@ static bool rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock, assert(fire_at_ns); assert(cb); - uint64_t now_us = timer_time_us_64(timer_hw); - if (NS_TO_US_ROUNDUP(fire_at_ns) > now_us && - (NS_TO_US_ROUNDUP(fire_at_ns) - now_us) > UINT32_MAX) - /* Too far in the future. */ - return true; - trigger->alarmclock = alarmclock; trigger->fire_at_ns = fire_at_ns; trigger->cb = cb; trigger->cb_arg = cb_arg; bool saved = cr_save_and_disable_interrupts(); + struct alarmclock_trigger **dst = &alarmclock->queue; while (*dst && fire_at_ns >= (*dst)->fire_at_ns) dst = &(*dst)->next; @@ -107,6 +105,7 @@ static bool rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock, if (*dst) (*dst)->prev = trigger; *dst = trigger; + if (!alarmclock->initialized) { hw_set_bits(&timer_hw->inte, 1 << alarmclock->alarm_num); irq_set_exclusive_handler(TIMER_ALARM_IRQ_NUM(timer_hw, alarmclock->alarm_num), @@ -114,6 +113,7 @@ static bool rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock, irq_set_enabled(TIMER_ALARM_IRQ_NUM(timer_hw, alarmclock->alarm_num), true); alarmclock->initialized = true; } + if (alarmclock->queue == trigger) { /* "Force" the interrupt handler to trigger as soon as * we enable interrupts. This handles the case of @@ -126,9 +126,8 @@ static bool rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock, * fire. */ hw_set_bits(&timer_hw->intf, 1 << alarmclock->alarm_num); } - cr_restore_interrupts(saved); - return false; + cr_restore_interrupts(saved); } static void rp2040_hwtimer_del_trigger(struct rp2040_hwtimer *alarmclock, diff --git a/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h index 4951136..cbd8dcf 100644 --- a/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h +++ b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h @@ -9,7 +9,7 @@ #include <pico/binary_info.h> /* for bi_* */ -#include <libcr_ipc/sema.h> +#include <libcr/coroutine.h> #include <libmisc/private.h> #include <libhw/generic/spi.h> @@ -33,7 +33,7 @@ struct rp2040_hwspi { /* mutable */ uint64_t dead_until_ns; - cr_sema_t sema; + cid_t waiter; END_PRIVATE(LIBHW_RP2040_HWSPI_H); }; LO_IMPLEMENTATION_H(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi); @@ -99,7 +99,7 @@ LO_IMPLEMENTATION_H(spi, struct rp2040_hwspi, rp2040_hwspi); min_delay_ns, bogus_data, \ pin_miso, pin_mosi, pin_clk, pin_cs, \ dma1, dma2, dma3, dma4); \ - } while(0) + } while (0) void _rp2040_hwspi_init(struct rp2040_hwspi *self, enum rp2040_hwspi_instance inst_num, enum spi_mode mode, diff --git a/libhw_cr/rp2040_include/libhw/w5500.h b/libhw_cr/rp2040_include/libhw/w5500.h index 8dda1a1..43c58a3 100644 --- a/libhw_cr/rp2040_include/libhw/w5500.h +++ b/libhw_cr/rp2040_include/libhw/w5500.h @@ -41,19 +41,6 @@ struct _w5500_socket { END_PRIVATE(LIBHW_W5500_H); }; -LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_tcplist); -LO_IMPLEMENTATION_H(net_stream_listener, struct _w5500_socket, w5500_tcplist); - -LO_IMPLEMENTATION_H(io_reader, struct _w5500_socket, w5500_tcp); -LO_IMPLEMENTATION_H(io_writer, struct _w5500_socket, w5500_tcp); -LO_IMPLEMENTATION_H(io_readwriter, struct _w5500_socket, w5500_tcp); -LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_tcp); -LO_IMPLEMENTATION_H(io_bidi_closer, struct _w5500_socket, w5500_tcp); -LO_IMPLEMENTATION_H(net_stream_conn, struct _w5500_socket, w5500_tcp); - -LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_udp); -LO_IMPLEMENTATION_H(net_packet_conn, struct _w5500_socket, w5500_udp); - struct w5500 { BEGIN_PRIVATE(LIBHW_W5500_H); /* const-after-init */ diff --git a/libhw_cr/w5500.c b/libhw_cr/w5500.c index c04e344..5189512 100644 --- a/libhw_cr/w5500.c +++ b/libhw_cr/w5500.c @@ -8,6 +8,7 @@ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.h * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.c * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/socket.c + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c * * Copyright (c) 2013, WIZnet Co., LTD. * All rights reserved. @@ -67,19 +68,19 @@ * SPDX-License-Identifier: MIT */ -#include <inttypes.h> /* for PRIu{n} */ +#include <string.h> /* for memcmp() */ /* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being * pico-sdk-specific. */ -#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ #include "rp2040_gpioirq.h" +#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ #include <libcr/coroutine.h> /* for cr_yield() */ #include <libhw/generic/alarmclock.h> /* for sleep_*() */ #define LOG_NAME W5500 -#include <libmisc/log.h> /* for errorf(), debugf(), const_byte_str() */ +#include <libmisc/log.h> #define IMPLEMENTATION_FOR_LIBHW_W5500_H YES #include <libhw/w5500.h> @@ -124,22 +125,21 @@ static const char *w5500_state_str(uint8_t state) { } } -/* libobj *********************************************************************/ +/* libmisc/obj.h **************************************************************/ -LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcplist, static); -LO_IMPLEMENTATION_C(net_stream_listener, struct _w5500_socket, w5500_tcplist, static); +LO_IMPLEMENTATION_STATIC(io_closer, struct _w5500_socket, w5500_tcplist); +LO_IMPLEMENTATION_STATIC(net_stream_listener, struct _w5500_socket, w5500_tcplist); -LO_IMPLEMENTATION_C(io_reader, struct _w5500_socket, w5500_tcp, static); -LO_IMPLEMENTATION_C(io_writer, struct _w5500_socket, w5500_tcp, static); -LO_IMPLEMENTATION_C(io_readwriter, struct _w5500_socket, w5500_tcp, static); -LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcp, static); -LO_IMPLEMENTATION_C(io_bidi_closer, struct _w5500_socket, w5500_tcp, static); -LO_IMPLEMENTATION_C(net_stream_conn, struct _w5500_socket, w5500_tcp, static); +LO_IMPLEMENTATION_STATIC(io_reader, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_STATIC(io_writer, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_STATIC(io_closer, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_STATIC(io_bidi_closer, struct _w5500_socket, w5500_tcp); +LO_IMPLEMENTATION_STATIC(net_stream_conn, struct _w5500_socket, w5500_tcp); -LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_udp, static); -LO_IMPLEMENTATION_C(net_packet_conn, struct _w5500_socket, w5500_udp, static); +LO_IMPLEMENTATION_STATIC(io_closer, struct _w5500_socket, w5500_udp); +LO_IMPLEMENTATION_STATIC(net_packet_conn, struct _w5500_socket, w5500_udp); -LO_IMPLEMENTATION_C(net_iface, struct w5500, w5500_if, static); +LO_IMPLEMENTATION_C(net_iface, struct w5500, w5500_if); /* mid-level utilities ********************************************************/ @@ -187,7 +187,7 @@ static COROUTINE w5500_irq_cr(void *_chip) { bool had_intr = false; uint8_t chipintr = w5500ll_read_common_reg(chip->spidev, chip_interrupt); - n_debugf(W5500_LL, "w5500_irq_cr(): chipintr=%"PRIu8, chipintr); + log_n_debugln(W5500_LL, "w5500_irq_cr(): chipintr=", chipintr); had_intr = had_intr || (chipintr != 0); if (chipintr) w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF); @@ -196,7 +196,7 @@ static COROUTINE w5500_irq_cr(void *_chip) { struct _w5500_socket *socket = &chip->sockets[socknum]; uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); - n_debugf(W5500_LL, "w5500_irq_cr(): sockintr[%"PRIu8"]=%"PRIu8, socknum, sockintr); + log_n_debugln(W5500_LL, "w5500_irq_cr(): sockintr[", socknum, "]=", sockintr); had_intr = had_intr || (sockintr != 0); switch (socket->mode) { @@ -208,15 +208,15 @@ static COROUTINE w5500_irq_cr(void *_chip) { recv_bits = sockintr & (SOCKINTR_RECV_DAT|SOCKINTR_RECV_FIN); if (listen_bits) { - debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->listen_sema", socknum); + log_debugln("w5500_irq_cr(): signal sock[", socknum, "]->listen_sema"); cr_sema_signal(&socket->listen_sema); } if (recv_bits) { - debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->read_sema", socknum); + log_debugln("w5500_irq_cr(): signal sock[", socknum, "]->read_sema"); cr_sema_signal(&socket->read_sema); } if (send_bits) { - debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->write_ch", socknum); + log_debugln("w5500_irq_cr(): signal sock[", socknum, "]->write_ch"); cr_chan_send(&socket->write_ch, send_bits); } break; @@ -228,7 +228,7 @@ static COROUTINE w5500_irq_cr(void *_chip) { cr_mutex_unlock(&chip->mu); if (!had_intr && gpio_get(chip->pin_intr)) { - debugf("w5500_irq_cr(): looks like all interrupts have been processed, sleeping..."); + log_debugln("w5500_irq_cr(): looks like all interrupts have been processed, sleeping..."); cr_sema_wait(&chip->intr); } else cr_yield(); @@ -283,7 +283,7 @@ static inline void w5500_socket_close(struct _w5500_socket *socket) { static void w5500_intrhandler(void *_chip, uint LM_UNUSED(gpio), enum gpio_irq_level LM_UNUSED(event)) { struct w5500 *chip = _chip; - debugf("w5500_intrhandler()"); + log_debugln("w5500_intrhandler()"); cr_sema_signal_from_intrhandler(&chip->intr); } @@ -322,7 +322,7 @@ void _w5500_init(struct w5500 *chip, w5500ll_write_sock_reg(chip->spidev, 0, mode, a); uint8_t b = w5500ll_read_sock_reg(chip->spidev, 0, mode); if (b != a) { - errorf("SPI to W5500 does not appear to be functional: wrote:0x%02"PRIx16" != read:0x%02"PRIx8, a, b); + log_errorln("SPI to W5500 does not appear to be functional: wrote:", (base16_u8_, a), " != read:", (base16_u8_, b)); spi_ok = false; } } @@ -337,7 +337,8 @@ void _w5500_init(struct w5500 *chip, w5500_hard_reset(chip); /* Finally, wire in the interrupt handler. */ - coroutine_add("w5500_irq", w5500_irq_cr, chip); + cid_t cid = coroutine_add("w5500_irq", w5500_irq_cr, chip); + assert(cid); } /* chip methods ***************************************************************/ @@ -408,7 +409,7 @@ void w5500_soft_reset(struct w5500 *chip) { cr_mutex_unlock(&chip->mu); } -static struct net_eth_addr w5500_if_hwaddr(struct w5500 *chip) { +struct net_eth_addr w5500_if_hwaddr(struct w5500 *chip) { assert(chip); return chip->hwaddr; @@ -426,28 +427,28 @@ static void _w5500_if_up(struct w5500 *chip, struct net_iface_config cfg) { cr_mutex_unlock(&chip->mu); } -static void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) { - debugf("if_up()"); - debugf(":: addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.addr)); - debugf(":: gateway_addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.gateway_addr)); - debugf(":: subnet_mask = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.subnet_mask)); +void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) { + log_debugln("if_up()"); + log_debugln(":: addr = ", (net_ip4_addr, cfg.addr)); + log_debugln(":: gateway_addr = ", (net_ip4_addr, cfg.gateway_addr)); + log_debugln(":: subnet_mask = ", (net_ip4_addr, cfg.subnet_mask)); _w5500_if_up(chip, cfg); } -static void w5500_if_ifdown(struct w5500 *chip) { - debugf("if_down()"); - _w5500_if_up(chip, (struct net_iface_config){0}); +void w5500_if_ifdown(struct w5500 *chip) { + log_debugln("if_down()"); + _w5500_if_up(chip, (struct net_iface_config){}); } -static lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, uint16_t local_port) { +lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, uint16_t local_port) { assert(chip); struct _w5500_socket *sock = w5500_alloc_socket(chip); if (!sock) { - debugf("tcp_listen() => no sock"); + log_debugln("tcp_listen() => no sock"); return LO_NULL(net_stream_listener); } - debugf("tcp_listen() => sock[%"PRIu8"]", sock->socknum); + log_debugln("tcp_listen() => sock[", sock->socknum, "]"); if (!local_port) local_port = w5500_alloc_local_port(chip); @@ -458,11 +459,11 @@ static lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, sock->read_deadline_ns = 0; sock->list_open = true; - return lo_box_w5500_tcplist_as_net_stream_listener(sock); + return LO_BOX(net_stream_listener, sock); } -static lo_interface net_stream_conn w5500_if_tcp_dial(struct w5500 *chip, - struct net_ip4_addr node, uint16_t port) { +net_stream_conn_or_error w5500_if_tcp_dial(struct w5500 *chip, + struct net_ip4_addr node, uint16_t port) { assert(chip); assert(memcmp(node.octets, net_ip4_addr_zero.octets, 4)); assert(memcmp(node.octets, net_ip4_addr_broadcast.octets, 4)); @@ -470,11 +471,11 @@ static lo_interface net_stream_conn w5500_if_tcp_dial(struct w5500 *chip, struct _w5500_socket *socket = w5500_alloc_socket(chip); if (!socket) { - debugf("tcp_dial() => no sock"); - return LO_NULL(net_stream_conn); + log_debugln("tcp_dial() => no sock"); + return ERROR_NEW_ERR(net_stream_conn, error_new(E_POSIX_ENOTSOCK)); } uint8_t socknum = socket->socknum; - debugf("tcp_dial() => sock[%"PRIu8"]", socknum); + log_debugln("tcp_dial() => sock[", socknum, "]"); uint16_t local_port = w5500_alloc_local_port(chip); @@ -502,29 +503,29 @@ static lo_interface net_stream_conn w5500_if_tcp_dial(struct w5500 *chip, cr_mutex_unlock(&chip->mu); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - debugf("tcp_dial(): state=%s", w5500_state_str(state)); + log_debugln("tcp_dial(): state=", w5500_state_str(state)); switch (state) { case STATE_TCP_SYNSENT: cr_yield(); break; case STATE_TCP_ESTABLISHED: - return lo_box_w5500_tcp_as_net_stream_conn(socket); + return ERROR_NEW_VAL(net_stream_conn, LO_BOX(net_stream_conn, socket)); default: goto restart; } } } -static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16_t local_port) { +lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16_t local_port) { assert(chip); struct _w5500_socket *socket = w5500_alloc_socket(chip); if (!socket) { - debugf("udp_conn() => no sock"); + log_debugln("udp_conn() => no sock"); return LO_NULL(net_packet_conn); } uint8_t socknum = socket->socknum; - debugf("udp_conn() => sock[%"PRIu8"]", socknum); + log_debugln("udp_conn() => sock[", socknum, "]"); if (!local_port) local_port = w5500_alloc_local_port(chip); @@ -544,18 +545,34 @@ static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16 cr_yield(); cr_mutex_unlock(&chip->mu); - return lo_box_w5500_udp_as_net_packet_conn(socket); + return LO_BOX(net_packet_conn, socket); +} + +bool w5500_if_arp_ping(struct w5500 *chip, struct net_ip4_addr addr) { + /* FIXME: This arp_ping implementation is really bad (and + * assumes that a UDP socket is open, which is "safe" because + * I only use it from inside of a DHCP client). */ + assert(chip); + struct _w5500_socket *sock = NULL; + for (size_t i = 0; i < LM_ARRAY_LEN(chip->sockets) && !sock; i++) { + if (chip->sockets[i].mode == W5500_MODE_UDP) + sock = &chip->sockets[i]; + } + assert(sock); + error err = w5500_udp_sendto(sock, "BOGUS_PACKET_TO_TRIGGER_AN_ARP", 17, addr, 5000); + log_debugln("arp_ping => ", (error, err)); + return err.num != E_NET_EARP_TIMEOUT; } /* tcp_listener methods *******************************************************/ -static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *socket) { +static net_stream_conn_or_error w5500_tcplist_accept(struct _w5500_socket *socket) { ASSERT_SELF(stream_listener, TCP); restart: if (!socket->list_open) { - debugf("tcp_listener.accept() => already closed"); - return LO_NULL(net_stream_conn); + log_debugln("tcp_listener.accept() => already closed"); + return ERROR_NEW_ERR(net_stream_conn, error_new(E_NET_ECLOSED)); } cr_mutex_lock(&chip->mu); @@ -573,7 +590,7 @@ static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *s cr_mutex_unlock(&chip->mu); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - debugf("tcp_listener.accept() => state=%s", w5500_state_str(state)); + log_debugln("tcp_listener.accept() => state=", w5500_state_str(state)); switch (state) { case STATE_TCP_LISTEN: case STATE_TCP_SYNRECV: @@ -581,37 +598,36 @@ static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *s break; case STATE_TCP_ESTABLISHED: socket->read_open = true; - /* fall-through */ + [[fallthrough]]; case STATE_TCP_CLOSE_WAIT: socket->write_open = true; - return lo_box_w5500_tcp_as_net_stream_conn(socket); + return ERROR_NEW_VAL(net_stream_conn, LO_BOX(net_stream_conn, socket)); default: goto restart; } } } -static int w5500_tcplist_close(struct _w5500_socket *socket) { - debugf("tcp_listener.close()"); +static error w5500_tcplist_close(struct _w5500_socket *socket) { + log_debugln("tcp_listener.close()"); ASSERT_SELF(stream_listener, TCP); socket->list_open = false; w5500_tcp_maybe_free(chip, socket); - return 0; + return ERROR_NULL; } /* tcp_conn methods ***********************************************************/ -static ssize_t w5500_tcp_writev(struct _w5500_socket *socket, const struct iovec *iov, int iovcnt) { +static size_t_and_error w5500_tcp_writev(struct _w5500_socket *socket, const struct wr_iovec *iov, int iovcnt) { assert(iov); assert(iovcnt > 0); size_t count = 0; for (int i = 0; i < iovcnt; i++) count += iov[i].iov_len; - debugf("tcp_conn.write(%zu)", count); + assert(count); + log_debugln("tcp_conn.write(", count, ")"); ASSERT_SELF(stream_conn, TCP); - if (count == 0) - return 0; /* What we really want is to pause until we receive an ACK for * some data we just queued, so that we can line up some new @@ -636,15 +652,15 @@ static ssize_t w5500_tcp_writev(struct _w5500_socket *socket, const struct iovec size_t done = 0; while (done < count) { if (!socket->write_open) { - debugf(" => soft closed"); - return -NET_ECLOSED; + log_debugln(" => soft closed"); + return ERROR_AND(size_t, done, error_new(E_NET_ECLOSED)); } cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) { cr_mutex_unlock(&chip->mu); - debugf(" => hard closed"); - return -NET_ECLOSED; + log_debugln(" => hard closed"); + return ERROR_AND(size_t, done, error_new(E_NET_ECLOSED)); } uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); @@ -668,24 +684,24 @@ static ssize_t w5500_tcp_writev(struct _w5500_socket *socket, const struct iovec cr_mutex_unlock(&chip->mu); switch (cr_chan_recv(&socket->write_ch)) { case SOCKINTR_SEND_OK: - debugf(" => sent %zu", freesize); + log_debugln(" => sent ", freesize); done += freesize; break; case SOCKINTR_SEND_TIMEOUT: - debugf(" => ACK timeout"); - return -NET_EACK_TIMEOUT; + log_debugln(" => ACK timeout"); + return ERROR_AND(size_t, done, error_new(E_NET_EACK_TIMEOUT)); case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT: assert_notreached("send both OK and timed out?"); default: assert_notreached("invalid write_ch bits"); } } - debugf(" => send finished"); - return done; + log_debugln(" => send finished"); + return ERROR_AND(size_t, done, ERROR_NULL); } static void w5500_tcp_set_read_deadline(struct _w5500_socket *socket, uint64_t ns) { - debugf("tcp_conn.set_read_deadline(%"PRIu64")", ns); + log_debugln("tcp_conn.set_read_deadline(", ns, ")"); ASSERT_SELF(stream_conn, TCP); socket->read_deadline_ns = ns; } @@ -695,16 +711,15 @@ static void w5500_tcp_alarm_handler(void *_arg) { cr_sema_signal_from_intrhandler(&socket->read_sema); } -static ssize_t w5500_tcp_readv(struct _w5500_socket *socket, const struct iovec *iov, int iovcnt) { +static size_t_or_error w5500_tcp_readv(struct _w5500_socket *socket, const struct rd_iovec *iov, int iovcnt) { assert(iov); assert(iovcnt > 0); size_t count = 0; for (int i = 0; i < iovcnt; i++) count += iov[i].iov_len; - debugf("tcp_conn.read(%zu)", count); + assert(count); + log_debugln("tcp_conn.read(", count, ")"); ASSERT_SELF(stream_conn, TCP); - if (count == 0) - return 0; struct alarmclock_trigger trigger = {}; if (socket->read_deadline_ns) @@ -718,13 +733,13 @@ static ssize_t w5500_tcp_readv(struct _w5500_socket *socket, const struct iovec for (;;) { if (!socket->read_open) { LO_CALL(bootclock, del_trigger, &trigger); - debugf(" => soft closed"); - return -NET_ECLOSED; + log_debugln(" => soft closed"); + return ERROR_NEW_ERR(size_t, error_new(E_NET_ECLOSED)); } if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) { LO_CALL(bootclock, del_trigger, &trigger); - debugf(" => recv timeout"); - return -NET_ERECV_TIMEOUT; + log_debugln(" => recv timeout"); + return ERROR_NEW_ERR(size_t, error_new(E_NET_ERECV_TIMEOUT)); } cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); @@ -736,8 +751,8 @@ static ssize_t w5500_tcp_readv(struct _w5500_socket *socket, const struct iovec default: LO_CALL(bootclock, del_trigger, &trigger); cr_mutex_unlock(&chip->mu); - debugf(" => hard closed"); - return -NET_ECLOSED; + log_debugln(" => hard closed"); + return ERROR_NEW_ERR(size_t, error_new(E_NET_ECLOSED)); } avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); @@ -747,15 +762,15 @@ static ssize_t w5500_tcp_readv(struct _w5500_socket *socket, const struct iovec if (state == STATE_TCP_CLOSE_WAIT) { LO_CALL(bootclock, del_trigger, &trigger); cr_mutex_unlock(&chip->mu); - debugf(" => EOF"); - return 0; + log_debugln(" => EOF"); + return ERROR_NEW_ERR(size_t, error_new(E_EOF)); } cr_mutex_unlock(&chip->mu); cr_sema_wait(&socket->read_sema); } assert(avail); - debugf(" => received %"PRIu16" bytes", avail); + log_debugln(" => received ", avail, " bytes"); uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); /* Read the data. */ if ((size_t)avail > count) @@ -767,11 +782,11 @@ static ssize_t w5500_tcp_readv(struct _w5500_socket *socket, const struct iovec /* Return. */ LO_CALL(bootclock, del_trigger, &trigger); cr_mutex_unlock(&chip->mu); - return avail; + return ERROR_NEW_VAL(size_t, avail); } -static int w5500_tcp_close_inner(struct _w5500_socket *socket, bool rd, bool wr) { - debugf("tcp_conn.close(rd=%s, wr=%s)", rd ? "true" : "false", wr ? "true" : "false"); +static error w5500_tcp_close_inner(struct _w5500_socket *socket, bool rd, bool wr) { + log_debugln("tcp_conn.close(rd=", rd, ", wr=", wr, ")"); ASSERT_SELF(stream_conn, TCP); if (rd) @@ -798,26 +813,26 @@ static int w5500_tcp_close_inner(struct _w5500_socket *socket, bool rd, bool wr) } w5500_tcp_maybe_free(chip, socket); - return 0; + return ERROR_NULL; } -static int w5500_tcp_close(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, true); } -static int w5500_tcp_close_read(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, false); } -static int w5500_tcp_close_write(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, false, true); } +static error w5500_tcp_close(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, true); } +static error w5500_tcp_close_read(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, false); } +static error w5500_tcp_close_write(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, false, true); } /* udp_conn methods ***********************************************************/ -static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t count, - struct net_ip4_addr node, uint16_t port) { - debugf("udp_conn.sendto()"); +static error w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t count, + struct net_ip4_addr node, uint16_t port) { + log_debugln("udp_conn.sendto()"); ASSERT_SELF(packet_conn, UDP); assert(buf); assert(count); uint16_t bufsize = ((uint16_t)w5500ll_read_sock_reg(chip->spidev, socknum, tx_buf_size))*1024; if (count > bufsize) { - debugf(" => msg too large"); - return -NET_EMSGSIZE; + log_debugln(" => msg too large"); + return error_new(E_POSIX_EMSGSIZE); } for (;;) { @@ -825,8 +840,8 @@ static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_UDP) { cr_mutex_unlock(&chip->mu); - debugf(" => closed"); - return -NET_ECLOSED; + log_debugln(" => closed"); + return error_new(E_NET_ECLOSED); } uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); @@ -844,8 +859,8 @@ static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); /* Queue data to be sent. */ uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); - w5500ll_writev(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((struct iovec){ - .iov_base = buf, + w5500ll_writev(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((struct wr_iovec){ + .iov_write_from = buf, .iov_len = count, }), 1, 0, 0); w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+count)); @@ -855,11 +870,11 @@ static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t cr_mutex_unlock(&chip->mu); switch (cr_chan_recv(&socket->write_ch)) { case SOCKINTR_SEND_OK: - debugf(" => sent"); - return count; + log_debugln(" => sent"); + return ERROR_NULL; case SOCKINTR_SEND_TIMEOUT: - debugf(" => ARP timeout"); - return -NET_EARP_TIMEOUT; + log_debugln(" => ARP timeout"); + return error_new(E_NET_EARP_TIMEOUT); case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT: assert_notreached("send both OK and timed out?"); default: @@ -868,7 +883,7 @@ static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t } static void w5500_udp_set_recv_deadline(struct _w5500_socket *socket, uint64_t ns) { - debugf("udp_conn.set_recv_deadline(%"PRIu64")", ns); + log_debugln("udp_conn.set_recv_deadline(", ns, ")"); ASSERT_SELF(packet_conn, UDP); socket->read_deadline_ns = ns; } @@ -878,9 +893,9 @@ static void w5500_udp_alarm_handler(void *_arg) { cr_sema_signal_from_intrhandler(&socket->read_sema); } -static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_t count, - struct net_ip4_addr *ret_node, uint16_t *ret_port) { - debugf("udp_conn.recvfrom()"); +static size_t_or_error w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_t count, + struct net_ip4_addr *ret_node, uint16_t *ret_port) { + log_debugln("udp_conn.recvfrom()"); ASSERT_SELF(packet_conn, UDP); assert(buf); assert(count); @@ -897,15 +912,15 @@ static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_ for (;;) { if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) { LO_CALL(bootclock, del_trigger, &trigger); - debugf(" => recv timeout"); - return -NET_ERECV_TIMEOUT; + log_debugln(" => recv timeout"); + return ERROR_NEW_ERR(size_t, error_new(E_NET_ERECV_TIMEOUT)); } cr_mutex_lock(&chip->mu); uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_UDP) { LO_CALL(bootclock, del_trigger, &trigger); - debugf(" => hard closed"); - return -NET_ECLOSED; + log_debugln(" => hard closed"); + return ERROR_NEW_ERR(size_t, error_new(E_NET_ECLOSED)); } avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); @@ -922,8 +937,8 @@ static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_ * can't find in the datasheet where it describes * this; this is based off of socket.c:recvfrom(). */ uint8_t hdr[8]; - w5500ll_readv(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((struct iovec){ - .iov_base = hdr, + w5500ll_readv(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((struct rd_iovec){ + .iov_read_to = hdr, .iov_len = sizeof(hdr), }), 1, 0); if (ret_node) { @@ -935,12 +950,12 @@ static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_ if (ret_port) *ret_port = uint16be_decode(&hdr[4]); uint16_t len = uint16be_decode(&hdr[6]); - debugf(" => received %"PRIu16" bytes%s", len, len < avail-8 ? " (plus more messages)" : ""); + log_debugln(" => received ", len, " bytes", len < avail-8 ? " (plus more messages)" : ""); /* Now read the actual data. */ if (count > len) count = len; - w5500ll_readv(chip->spidev, ptr+8, CTL_BLOCK_SOCK(socknum, RX), &((struct iovec){ - .iov_base = buf, + w5500ll_readv(chip->spidev, ptr+8, CTL_BLOCK_SOCK(socknum, RX), &((struct rd_iovec){ + .iov_read_to = buf, .iov_len = len, }), 1, 0); /* Tell the chip that we read the data. */ @@ -949,14 +964,14 @@ static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_ /* Return. */ LO_CALL(bootclock, del_trigger, &trigger); cr_mutex_unlock(&chip->mu); - return len; + return ERROR_NEW_VAL(size_t, len); } -static int w5500_udp_close(struct _w5500_socket *socket) { - debugf("udp_conn.close()"); +static error w5500_udp_close(struct _w5500_socket *socket) { + log_debugln("udp_conn.close()"); ASSERT_SELF(packet_conn, UDP); w5500_socket_close(socket); w5500_free_socket(chip, socket); - return 0; + return ERROR_NULL; } diff --git a/libhw_cr/w5500_ll.c b/libhw_cr/w5500_ll.c new file mode 100644 index 0000000..ec40e5e --- /dev/null +++ b/libhw_cr/w5500_ll.c @@ -0,0 +1,116 @@ +/* libhw_cr/w5500_ll.c - Low-level library for the WIZnet W5500 chip + * + * Based entirely on the W5500 datasheet (v1.1.0), not on reference code. + * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcmp() and mempy() */ + +#include <libmisc/alloc.h> +#include <libmisc/fmt.h> +#include <libmisc/log.h> + +#include "w5500_ll.h" + +#if CONFIG_W5500_LL_DEBUG +static void fmt_print_ctl_block(lo_interface fmt_dest w, uint8_t b) { + static char *strs[] = { + "RES", + "REG", + "TX", + "RX", + }; + fmt_print(w, "CTL_BLOCK_SOCK(", (base10, (((b)>>5) & 0b111)), ", ", strs[((b)>>3)&0b11], ")"); +} +#endif + +void _w5500ll_n_writev(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct wr_iovec *iov, int iovcnt, + size_t skip, size_t max) +{ + assert(!LO_IS_NULL(spidev)); + assert((block & ~CTL_MASK_BLOCK) == 0); + assert(iov); + assert(iovcnt > 0); +#if CONFIG_W5500_LL_DEBUG + log_n_debugln(W5500_LL, + func, "(): w5500ll_write(spidev", + ", addr=", (base16_u16_, addr), + ", block=", (ctl_block, block), + ", iov", + ", iovcnt=", iovcnt, + ")"); +#endif + + uint8_t header[] = { + (uint8_t)((addr >> 8) & 0xFF), + (uint8_t)(addr & 0xFF), + (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, + }; + int inner_cnt = 1+io_wr_slice_cnt(iov, iovcnt, skip, max); + [[gnu::cleanup(heap_cleanup)]] struct duplex_iovec *inner = heap_alloc(inner_cnt, struct duplex_iovec); + inner[0] = (struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), + }; + io_slice_wr_to_duplex(&inner[1], iov, iovcnt, skip, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); +} + + +void _w5500ll_n_readv(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct rd_iovec *iov, int iovcnt, + size_t max) +{ + assert(!LO_IS_NULL(spidev)); + assert((block & ~CTL_MASK_BLOCK) == 0); + assert(iov); + assert(iovcnt > 0); +#if CONFIG_W5500_LL_DEBUG + log_n_debugln(W5500_LL, + func, "(): w5500ll_read(spidev", + ", addr=", (base16_u16_, addr), + ", block=", (ctl_block, block), + ", iov", + ", iovcnt=", iovcnt, + ")"); +#endif + + uint8_t header[] = { + (uint8_t)((addr >> 8) & 0xFF), + (uint8_t)(addr & 0xFF), + (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, + }; + int inner_cnt = 1+io_rd_slice_cnt(iov, iovcnt, 0, max); + [[gnu::cleanup(heap_cleanup)]] struct duplex_iovec *inner = heap_alloc(inner_cnt, struct duplex_iovec); + inner[0] = (struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), + }; + io_slice_rd_to_duplex(&inner[1], iov, iovcnt, 0, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); +} + +void _w5500ll_n_read_repeated(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + void *buf, void *buf_scratch, size_t len) +{ + _w5500ll_n_readv(func, spidev, addr, block, + &((struct rd_iovec){.iov_read_to=buf, .iov_len=len}), 1, + 0); + for (;;) { + _w5500ll_n_readv(func, spidev, addr, block, + &((struct rd_iovec){.iov_read_to=buf_scratch, .iov_len=len}), 1, + 0); + if (memcmp(buf, buf_scratch, len) == 0) + break; + memcpy(buf, buf_scratch, len); + } +} diff --git a/libhw_cr/w5500_ll.h b/libhw_cr/w5500_ll.h index 2506cd2..e375ebd 100644 --- a/libhw_cr/w5500_ll.h +++ b/libhw_cr/w5500_ll.h @@ -1,6 +1,6 @@ -/* libhw_cr/w5500_ll.h - Low-level header library for the WIZnet W5500 chip +/* libhw_cr/w5500_ll.h - Low-level library for the WIZnet W5500 chip * - * Based entirely on the W5500 datasheet, v1.1.0. + * Based entirely on the W5500 datasheet (v1.1.0), not on reference code. * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf * * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> @@ -10,17 +10,15 @@ #ifndef _LIBHW_CR_W5500_LL_H_ #define _LIBHW_CR_W5500_LL_H_ -#include <alloca.h> /* for alloca() */ #include <stdint.h> /* for uint{n}_t */ -#include <string.h> /* for memcmp() */ -#include <libmisc/assert.h> /* for assert(), static_assert() */ +#include <libmisc/assert.h> /* for static_assert() */ #include <libmisc/endian.h> /* for uint16be_t */ #include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */ #include <libhw/generic/spi.h> /* for lo_interface spi */ -/* Config *********************************************************************/ +/* Config. ***************************************************************************************/ #include "config.h" @@ -28,7 +26,7 @@ #error config.h must define CONFIG_W5500_LL_DEBUG #endif -/* Low-level protocol built on SPI frames. ***********************************/ +/* Low-level protocol built on SPI frames. *******************************************************/ /* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */ @@ -53,93 +51,81 @@ #define CTL_OM_FDM2 0b10 /* fixed-length data mode: 2 byte data length */ #define CTL_OM_FDM4 0b11 /* fixed-length data mode: 4 byte data length */ -#if CONFIG_W5500_LL_DEBUG -static char *_ctl_block_part_strs[] = { - "RES", - "REG", - "TX", - "RX", -}; -#define PRI_ctl_block "CTL_BLOCK_SOCK(%d, %s)" -#define ARG_ctl_block(b) (((b)>>5) & 0b111), _ctl_block_part_strs[((b)>>3)&0b11] -#endif - -/* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex. - * Lame. */ +/* Even though SPI is a full-duplex protocol, the W5500's spiframe on + * top of it is only half-duplex. Lame. */ -static inline void #if CONFIG_W5500_LL_DEBUG -#define w5500ll_writev(...) _w5500ll_writev(__func__, __VA_ARGS__) -_w5500ll_writev(const char *func, + #define w5500ll_writev(...) _w5500ll_n_writev (__func__, __VA_ARGS__) + #define w5500ll_readv(...) _w5500ll_n_readv (__func__, __VA_ARGS__) #else -w5500ll_writev( -#endif - lo_interface spi spidev, uint16_t addr, uint8_t block, - const struct iovec *iov, int iovcnt, - size_t skip, size_t max) -{ - assert(!LO_IS_NULL(spidev)); - assert((block & ~CTL_MASK_BLOCK) == 0); - assert(iov); - assert(iovcnt > 0); -#if CONFIG_W5500_LL_DEBUG - n_debugf(W5500_LL, - "%s(): w5500ll_write(spidev, addr=%#04x, block="PRI_ctl_block", iov, iovcnt=%d)", - func, addr, ARG_ctl_block(block), iovcnt); + #define _w5500ll_n_writev(func, ...) w5500ll_writev (__VA_ARGS__) + #define _w5500ll_n_readv(func, ...) w5500ll_readv (__VA_ARGS__) #endif - uint8_t header[] = { - (uint8_t)((addr >> 8) & 0xFF), - (uint8_t)(addr & 0xFF), - (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, - }; - int inner_cnt = 1+io_slice_cnt(iov, iovcnt, skip, max); - struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt); - inner[0] = (struct duplex_iovec){ - .iov_read_to = IOVEC_DISCARD, - .iov_write_from = header, - .iov_len = sizeof(header), - }; - io_slice_wr_to_duplex(&inner[1], iov, iovcnt, skip, max); - LO_CALL(spidev, readwritev, inner, inner_cnt); -} - -static inline void +/* `skip` and `max` correspond to the <libhw/generic/io.h> `slice` + * functions' `byte_start` and `max_byte_cnt` arguments for working on + * just a subset of the iovec list. */ + +void _w5500ll_n_writev(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct wr_iovec *iov, int iovcnt, + size_t skip, size_t max); +void _w5500ll_n_readv(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct rd_iovec *iov, int iovcnt, + size_t max); + +/* Operate on registers. *************************************************************************/ + +/* + * Given a `blocktyp` that is a struct describing the layout of + * registers in a block (e.g. `blocktyp = struct + * w5500_ll_block_common_reg`), read or write the `field` register + * (where `field` must be a field in that struct). + */ + +#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ + typeof((blocktyp){}.field) lval = val; \ + w5500ll_writev(spidev, \ + offsetof(blocktyp, field), blockid, \ + &((struct wr_iovec){ \ + .iov_write_from = &lval, \ + .iov_len = sizeof(lval), \ + }), 1, \ + 0, 0); \ + } while (0) + +/* The datasheet tells us that multi-byte reads are non-atomic and + * that "it is recommended that you read all 16-bits twice or more + * until getting the same value". */ #if CONFIG_W5500_LL_DEBUG -#define w5500ll_readv(...) _w5500ll_read(__func__, __VA_ARGS__) -_w5500ll_readv(const char *func, + #define _w5500ll_read_repeated(...) _w5500ll_n_read_repeated (__func__, __VA_ARGS__) #else -w5500ll_readv( -#endif - lo_interface spi spidev, uint16_t addr, uint8_t block, - const struct iovec *iov, int iovcnt, - size_t max) -{ - assert(!LO_IS_NULL(spidev)); - assert((block & ~CTL_MASK_BLOCK) == 0); - assert(iov); - assert(iovcnt > 0); -#if CONFIG_W5500_LL_DEBUG - n_debugf(W5500_LL, - "%s(): w5500ll_read(spidev, addr=%#04x, block="PRI_ctl_block", iov, iovcnt=%d)", - func, addr, ARG_ctl_block(block), iovcnt); + #define _w5500ll_n_read_repeated(func, ...) _w5500ll_read_repeated (__VA_ARGS__) #endif +void _w5500ll_n_read_repeated(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + void *buf, void *buf_scratch, size_t len); +#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ + typeof((blocktyp){}.field) val; \ + if (sizeof(val) == 1) { \ + w5500ll_readv(spidev, \ + offsetof(blocktyp, field), blockid, \ + &((struct rd_iovec){ \ + .iov_read_to = &val, \ + .iov_len = sizeof(val), \ + }), 1, \ + 0); \ + } else { \ + typeof(val) val2; \ + _w5500ll_read_repeated(spidev, \ + offsetof(blocktyp, field), blockid, \ + &val, &val2, sizeof(val)); \ + } \ + val; \ + }) - uint8_t header[] = { - (uint8_t)((addr >> 8) & 0xFF), - (uint8_t)(addr & 0xFF), - (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, - }; - int inner_cnt = 1+io_slice_cnt(iov, iovcnt, 0, max); - struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt); - inner[0] = (struct duplex_iovec){ - .iov_read_to = IOVEC_DISCARD, - .iov_write_from = header, - .iov_len = sizeof(header), - }; - io_slice_rd_to_duplex(&inner[1], iov, iovcnt, 0, max); - LO_CALL(spidev, readwritev, inner, inner_cnt); -} +/* Register blocks. ******************************************************************************/ /* Common chip-wide registers. ***********************************************/ @@ -378,49 +364,4 @@ static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); struct w5500ll_block_sock_reg, \ field) -/******************************************************************************/ - -#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ - typeof((blocktyp){}.field) lval = val; \ - w5500ll_writev(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &((struct iovec){ \ - &lval, \ - sizeof(lval), \ - }), \ - 1, 0, 0); \ - } while (0) - -/* The datasheet tells us that multi-byte reads are non-atomic and - * that "it is recommended that you read all 16-bits twice or more - * until getting the same value". */ -#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ - typeof((blocktyp){}.field) val; \ - w5500ll_readv(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &((struct iovec){ \ - .iov_base = &val, \ - .iov_len = sizeof(val), \ - }), \ - 1, 0); \ - if (sizeof(val) > 1) \ - for (;;) { \ - typeof(val) val2; \ - w5500ll_readv(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &((struct iovec){ \ - .iov_base = &val2, \ - .iov_len = sizeof(val), \ - }), \ - 1, 0); \ - if (memcmp(&val2, &val, sizeof(val)) == 0) \ - break; \ - val = val2; \ - } \ - val; \ - }) - #endif /* _LIBHW_CR_W5500_LL_H_ */ |