38 Commits

Author SHA1 Message Date
Ian Gulliver e2a5d97dae Zero-copy TX: span_writer packer, static buffers, no vector returns 2026-04-10 22:18:44 +09:00
Ian Gulliver 94895fd2fe Move picoboot reboot into handler via dispatch_schedule_ms 2026-04-10 22:04:38 +09:00
Ian Gulliver f2d98ef4f1 Skip net_poll and timers unless their interrupt has fired 2026-04-10 22:00:34 +09:00
Ian Gulliver 8edf8c2d4f Remove unused iodata_t, PIO_OFFSET_WRITE_BITS, trim intr_kind, move sock_count to cpp 2026-04-10 21:51:58 +09:00
Ian Gulliver c961499239 Remove socket IO mode (nonblock unnecessary with pre-checked recv buffer) 2026-04-10 21:45:43 +09:00
Ian Gulliver 0d41f63533 Strip non-MACRAW enums, unused macros, and set_socket_dest_mac 2026-04-10 21:42:20 +09:00
Ian Gulliver 394628b8da MACRAW-only: simplify send/recv API, remove non-MACRAW code paths and unused types 2026-04-10 21:37:03 +09:00
Ian Gulliver bee0fa3aef Remove critical section code, unused constants, and unused register definitions 2026-04-10 21:27:01 +09:00
Ian Gulliver ff9f9a5c1f Remove unused W6300 register accessors and internal functions 2026-04-10 21:18:43 +09:00
Ian Gulliver af308b5aac Remove unused W6300 API: phy, net config, TCP, keepalive, peek, and related types 2026-04-10 21:06:52 +09:00
Ian Gulliver 712110aace Remove unused interrupt code: enable_interrupt_pin, CHECK markers, dead comments 2026-04-10 20:54:07 +09:00
Ian Gulliver 0c11cbb1d1 WFI with interrupt-driven wakeup for USB and W6300 2026-04-10 12:55:04 +09:00
Ian Gulliver 3d20bf4c33 Per-packet source tracking in transport, via/from in info output 2026-04-07 22:30:26 +09:00
Ian Gulliver f96ed20aa0 Fix broadcast collection: UDP reader blocks until deadline, 500ms timeout everywhere 2026-04-07 22:22:19 +09:00
Ian Gulliver e301c672a9 Broadcast discovery with InfoAll, interface broadcast detection, clean output 2026-04-07 22:12:20 +09:00
Ian Gulliver 7034391d4d UDP transport with broadcast support, -udp and -iface flags on info 2026-04-07 21:44:35 +09:00
Ian Gulliver 9989d8c66a Add UDP transport with picomap port 28781, info -udp flag 2026-04-07 21:36:50 +09:00
Ian Gulliver 3d749add7d Compose eth header into ipv4_header, use icmp_echo struct, protocol switch in handle_ipv4 2026-04-07 12:31:27 +09:00
Ian Gulliver f161dda60a Rewrite net.cpp to use packed structs for frame encoding/decoding 2026-04-07 12:21:41 +09:00
Ian Gulliver b0294fada3 Fix broadcast ping reply source IP, accept broadcast destinations 2026-04-07 12:18:07 +09:00
Ian Gulliver 31b2c16b07 Debug log with dlog_if_slow, MACRAW ping working, wfi disabled 2026-04-07 12:09:18 +09:00
Ian Gulliver d215ddc6f2 Add debug log ring buffer with timestamps, log CLI subcommand 2026-04-07 09:18:43 +09:00
Ian Gulliver ffcbaf0665 Merge info/load/test CLIs into unified picomap subcommand CLI 2026-04-07 08:37:34 +09:00
Ian Gulliver a7381ca435 MACRAW net stack, slog, load -target flag, LED blink, net_poll disabled pending SPI fix 2026-04-07 08:34:29 +09:00
Ian Gulliver a9193d51e4 Switch commands to slog, disable net_poll pending fix 2026-04-07 07:43:16 +09:00
Ian Gulliver 46db2fd966 MACRAW IP stack with ARP/ICMP, stable device ordering, LED blink on test 2026-04-07 07:34:24 +09:00
Ian Gulliver 642e2ff318 Remove leading underscores and mixed case from w6300 internal symbols 2026-04-07 07:15:24 +09:00
Ian Gulliver 3c320cf466 Rename screaming case symbols to snake_case in w6300 public API 2026-04-07 07:09:11 +09:00
Ian Gulliver 843a286631 Remove multicast socket polling, fix USB responsiveness 2026-04-07 07:04:53 +09:00
Ian Gulliver e60479bad8 Switch to IPv4 zeroconf, add test framework with discovery test, fix serial enumeration 2026-04-07 06:58:39 +09:00
Ian Gulliver b8c0e6be66 Handlers return multiple responses, dispatch polls sockets for UDP commands 2026-04-06 20:22:40 +09:00
Ian Gulliver f837937cb7 Add firmware_name to info response 2026-04-06 20:09:30 +09:00
Ian Gulliver 49bbe1b29c Split dispatch init/run, join IPv6 multicast discovery group in firmware 2026-04-06 20:01:22 +09:00
Ian Gulliver 00b960d81d Add dispatch loop, copy_to_ram, retry picotool load without sleep 2026-04-06 17:36:41 +09:00
Ian Gulliver b197f0bfa7 Extract picoboot and info handlers into lib/handlers 2026-04-06 17:24:34 +09:00
Ian Gulliver 00ab432a72 Add IPv6 link-local address to net_init and info response, use WaitGroup.Go 2026-04-06 17:20:13 +09:00
Ian Gulliver 1fa1b2076c Poll for BOOTSEL readiness instead of fixed sleep, load with -x 2026-04-06 09:19:26 +09:00
Ian Gulliver ee8563ab69 Remove DHCP client, preserved on dhcp-wip branch 2026-04-06 09:10:50 +09:00
29 changed files with 1643 additions and 2057 deletions
+5 -3
View File
@@ -4,8 +4,10 @@ description: Build firmware, load it onto the Pico, and reboot. Use when the use
user-invocable: true
---
Run `go run ./cmd/load/` from the project root. This builds both firmware targets, loads picomap onto the first device and picomap_test onto the second, and reboots both.
Run `go run ./cmd/picomap/ load` from the project root. This builds both firmware targets, loads picomap onto the first device and picomap_test onto the second, and reboots both.
After loading, run `go run ./cmd/info/` to verify both devices are responding.
To load a single target: `go run ./cmd/picomap/ load picomap` or `go run ./cmd/picomap/ load picomap_test`.
After modifying the load command itself (cmd/load/, lib/wire/, lib/picoserial/), run it twice: once to load the firmware, once to verify the load process still works end-to-end.
After loading, run `go run ./cmd/picomap/ info` to verify devices are responding.
After modifying the CLI itself (cmd/picomap/, lib/), run load twice: once to load the firmware, once to verify the load process still works end-to-end.
-69
View File
@@ -1,69 +0,0 @@
package main
import (
"fmt"
"os"
"sync"
"time"
"github.com/theater/picomap/lib/client"
)
type deviceResult struct {
dev string
info *client.ResponseInfo
err error
}
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func run() error {
devs, err := client.ListSerial()
if err != nil {
return err
}
if len(devs) == 0 {
return fmt.Errorf("no devices found")
}
results := make([]deviceResult, len(devs))
var wg sync.WaitGroup
for i, dev := range devs {
wg.Add(1)
go func() {
defer wg.Done()
results[i].dev = dev
c, err := client.NewSerial(dev, 2*time.Second)
if err != nil {
results[i].err = err
return
}
info, err := c.Info()
c.Close()
results[i].info = info
results[i].err = err
}()
}
wg.Wait()
for _, r := range results {
fmt.Printf("Device: %s\n", r.dev)
if r.err != nil {
fmt.Fprintf(os.Stderr, " error: %v\n", r.err)
continue
}
fmt.Printf(" Board ID: %02X%02X%02X%02X%02X%02X%02X%02X\n",
r.info.BoardID[0], r.info.BoardID[1], r.info.BoardID[2], r.info.BoardID[3],
r.info.BoardID[4], r.info.BoardID[5], r.info.BoardID[6], r.info.BoardID[7])
fmt.Printf(" MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
r.info.MAC[0], r.info.MAC[1], r.info.MAC[2],
r.info.MAC[3], r.info.MAC[4], r.info.MAC[5])
}
return nil
}
-158
View File
@@ -1,158 +0,0 @@
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"time"
"github.com/theater/picomap/lib/client"
"github.com/theater/picomap/lib/picotool"
)
func main() {
wd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
buildDir := filepath.Join(wd, "firmware", "build")
if err := run(buildDir); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func build(buildDir string) error {
fmt.Println("Configuring...")
cmake := exec.Command("cmake", "-S", filepath.Join(filepath.Dir(buildDir)), "-B", buildDir)
cmake.Stdout = os.Stdout
cmake.Stderr = os.Stderr
if err := cmake.Run(); err != nil {
return fmt.Errorf("cmake failed: %w", err)
}
fmt.Println("Building...")
cmd := exec.Command("make", "-C", buildDir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("build failed: %w", err)
}
return nil
}
func boardSerial(id [8]byte) string {
return fmt.Sprintf("%02X%02X%02X%02X%02X%02X%02X%02X",
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7])
}
func run(buildDir string) error {
if err := build(buildDir); err != nil {
return err
}
devs, err := client.ListSerial()
if err != nil {
return err
}
if len(devs) < 2 {
return fmt.Errorf("expected 2 devices, found %d", len(devs))
}
serials := make([]string, 2)
errs := make([]error, 2)
var wg sync.WaitGroup
for i := range 2 {
wg.Add(1)
go func() {
defer wg.Done()
c, err := client.NewSerial(devs[i], 2*time.Second)
if err != nil {
errs[i] = err
return
}
info, err := c.Info()
c.Close()
if err != nil {
errs[i] = err
return
}
serials[i] = boardSerial(info.BoardID)
}()
}
wg.Wait()
for i, err := range errs {
if err != nil {
return fmt.Errorf("info %s: %w", devs[i], err)
}
}
fmt.Println("Sending PICOBOOT requests...")
for i := range 2 {
wg.Add(1)
go func() {
defer wg.Done()
c, err := client.NewSerial(devs[i], 2*time.Second)
if err != nil {
errs[i] = err
return
}
err = c.PICOBOOT()
c.Close()
if err != nil {
errs[i] = fmt.Errorf("PICOBOOT %s: %w", devs[i], err)
}
}()
}
wg.Wait()
for _, err := range errs {
if err != nil {
return err
}
}
time.Sleep(2 * time.Second)
uf2s := []string{
filepath.Join(buildDir, "picomap.uf2"),
filepath.Join(buildDir, "picomap_test.uf2"),
}
fmt.Println("Loading firmware...")
for i := range 2 {
wg.Add(1)
go func() {
defer wg.Done()
errs[i] = picotool.Load(uf2s[i], serials[i])
}()
}
wg.Wait()
for i, err := range errs {
if err != nil {
return fmt.Errorf("load %s: %w", serials[i], err)
}
}
fmt.Println("Rebooting...")
for i := range 2 {
wg.Add(1)
go func() {
defer wg.Done()
errs[i] = picotool.Reboot(serials[i])
}()
}
wg.Wait()
for i, err := range errs {
if err != nil {
return fmt.Errorf("reboot %s: %w", serials[i], err)
}
}
fmt.Println("Done.")
return nil
}
+392
View File
@@ -0,0 +1,392 @@
package main
import (
"encoding/hex"
"flag"
"fmt"
"log/slog"
"net"
"os"
"os/exec"
"path/filepath"
"sync"
"time"
"github.com/theater/picomap/lib/client"
"github.com/theater/picomap/lib/picotool"
)
func main() {
if len(os.Args) < 2 {
slog.Error("usage: picomap <info|load|test> [args...]")
os.Exit(1)
}
cmd := os.Args[1]
args := os.Args[2:]
var err error
switch cmd {
case "info":
err = cmdInfo(args)
case "load":
err = cmdLoad(args)
case "log":
err = cmdLog(args)
case "test":
err = cmdTest(args)
default:
slog.Error("usage: picomap <info|load|log|test> [args...]")
os.Exit(1)
}
if err != nil {
slog.Error("fatal", "err", err)
os.Exit(1)
}
}
type deviceResult struct {
dev string
info *client.ResponseInfo
err error
}
func printInfo(via string, from string, info *client.ResponseInfo) {
slog.Info("device",
"via", via,
"from", from,
"board_id", hex.EncodeToString(info.BoardID[:]),
"mac", net.HardwareAddr(info.MAC[:]).String(),
"ip", net.IP(info.IP[:]).String(),
"firmware", info.FirmwareName)
}
func cmdInfo(args []string) error {
fs := flag.NewFlagSet("info", flag.ExitOnError)
udpAddr := fs.String("udp", "", "connect via UDP to this IP address")
iface := fs.String("iface", "", "bind to this network interface (for broadcast)")
fs.Parse(args)
if *udpAddr == "" && *iface != "" {
bcast, err := client.InterfaceBroadcast(*iface)
if err != nil {
return err
}
*udpAddr = bcast
}
if *udpAddr != "" {
c, err := client.NewUDP(*udpAddr, *iface, 500*time.Millisecond)
if err != nil {
return err
}
defer c.Close()
infos, err := c.InfoAll()
if err != nil {
return err
}
if len(infos) == 0 {
return fmt.Errorf("no devices responded")
}
for _, r := range infos {
printInfo(*udpAddr, r.From, r.Value)
}
return nil
}
devs, err := client.ListSerial()
if err != nil {
return err
}
if len(devs) == 0 {
return fmt.Errorf("no devices found")
}
results := make([]deviceResult, len(devs))
var wg sync.WaitGroup
for i, dev := range devs {
results[i].dev = dev
wg.Go(func() {
c, err := client.NewSerial(dev, 500*time.Millisecond)
if err != nil {
results[i].err = err
return
}
info, err := c.Info()
c.Close()
if err != nil {
results[i].err = err
return
}
results[i].info = info
})
}
wg.Wait()
for _, r := range results {
if r.err != nil {
slog.Error("device error", "dev", r.dev, "err", r.err)
continue
}
printInfo(r.dev, r.dev, r.info)
}
return nil
}
func boardSerial(id [8]byte) string {
return fmt.Sprintf("%02X%02X%02X%02X%02X%02X%02X%02X",
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7])
}
type deviceInfo struct {
dev string
serial string
uf2 string
name string
}
func buildFirmware(buildDir string) error {
slog.Info("configuring")
cmake := exec.Command("cmake", "-S", filepath.Join(filepath.Dir(buildDir)), "-B", buildDir)
cmake.Stdout = os.Stdout
cmake.Stderr = os.Stderr
if err := cmake.Run(); err != nil {
return fmt.Errorf("cmake failed: %w", err)
}
slog.Info("building")
cmd := exec.Command("make", "-C", buildDir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("build failed: %w", err)
}
return nil
}
func cmdLog(_ []string) error {
devs, err := client.ListSerial()
if err != nil {
return err
}
if len(devs) == 0 {
return fmt.Errorf("no devices found")
}
for _, dev := range devs {
log := slog.With("dev", dev)
c, err := client.NewSerial(dev, 500*time.Millisecond)
if err != nil {
log.Error("connect error", "err", err)
continue
}
resp, err := c.Log()
c.Close()
if err != nil {
log.Error("log error", "err", err)
continue
}
if len(resp.Entries) == 0 {
log.Info("no debug messages")
continue
}
for _, e := range resp.Entries {
log.Info("dlog", "t_us", e.TimestampUS, "msg", e.Message)
}
}
return nil
}
func cmdLoad(args []string) error {
target := "all"
if len(args) > 0 {
target = args[0]
}
wd, err := os.Getwd()
if err != nil {
return err
}
buildDir := filepath.Join(wd, "firmware", "build")
if err := buildFirmware(buildDir); err != nil {
return err
}
devs, err := client.ListSerial()
if err != nil {
return err
}
allTargets := []struct {
name string
uf2 string
}{
{"picomap", filepath.Join(buildDir, "picomap.uf2")},
{"picomap_test", filepath.Join(buildDir, "picomap_test.uf2")},
}
var targets []struct {
name string
uf2 string
}
switch target {
case "all":
targets = allTargets
case "picomap":
targets = allTargets[:1]
case "picomap_test":
targets = allTargets[1:]
default:
return fmt.Errorf("unknown target %q", target)
}
if len(devs) < len(targets) {
return fmt.Errorf("need %d device(s), found %d", len(targets), len(devs))
}
devices := make([]deviceInfo, len(targets))
errs := make([]error, len(targets))
var wg sync.WaitGroup
for i := range targets {
log := slog.With("dev", devs[i])
wg.Go(func() {
c, err := client.NewSerial(devs[i], 500*time.Millisecond)
if err != nil {
errs[i] = err
return
}
info, err := c.Info()
c.Close()
if err != nil {
errs[i] = err
return
}
devices[i] = deviceInfo{
dev: devs[i],
serial: boardSerial(info.BoardID),
uf2: targets[i].uf2,
name: targets[i].name,
}
log.Info("got info", "serial", devices[i].serial, "firmware", info.FirmwareName)
})
}
wg.Wait()
for i, err := range errs {
if err != nil {
return fmt.Errorf("[%s] info: %w", devs[i], err)
}
}
for i := range devices {
log := slog.With("serial", devices[i].serial)
wg.Go(func() {
log.Info("sending PICOBOOT")
c, err := client.NewSerial(devices[i].dev, 500*time.Millisecond)
if err != nil {
errs[i] = err
return
}
err = c.PICOBOOT()
c.Close()
if err != nil {
errs[i] = fmt.Errorf("PICOBOOT %s: %w", devices[i].serial, err)
return
}
log.Info("PICOBOOT sent")
})
}
wg.Wait()
for i, err := range errs {
if err != nil {
return fmt.Errorf("[%s] %w", devices[i].serial, err)
}
}
uf2s := make([]string, len(targets))
for i := range targets {
uf2s[i] = targets[i].uf2
}
for i := range devices {
log := slog.With("serial", devices[i].serial)
wg.Go(func() {
log.Info("loading", "uf2", devices[i].name)
errs[i] = picotool.Load(devices[i].uf2, devices[i].serial, 10*time.Second)
if errs[i] == nil {
log.Info("loaded", "uf2", devices[i].name)
}
})
}
wg.Wait()
for i, err := range errs {
if err != nil {
return fmt.Errorf("[%s] load: %w", devices[i].serial, err)
}
}
slog.Info("done")
return nil
}
func cmdTest(args []string) error {
if len(args) < 1 {
return fmt.Errorf("usage: picomap test <name>")
}
name := args[0]
devs, err := client.ListSerial()
if err != nil {
return err
}
var testDev string
for _, dev := range devs {
log := slog.With("dev", dev)
c, err := client.NewSerial(dev, 500*time.Millisecond)
if err != nil {
log.Warn("connect error", "err", err)
continue
}
info, err := c.Info()
c.Close()
if err != nil {
log.Warn("info error", "err", err)
continue
}
log.Info("got info", "firmware", info.FirmwareName)
if info.FirmwareName == "picomap_test" {
testDev = dev
break
}
}
if testDev == "" {
return fmt.Errorf("no picomap_test device found")
}
log := slog.With("dev", testDev)
log.Info("running test", "name", name)
c, err := client.NewSerial(testDev, 10*time.Second)
if err != nil {
return err
}
defer c.Close()
result, err := c.Test(name)
if err != nil {
return fmt.Errorf("remote: %w", err)
}
for _, msg := range result.Messages {
log.Info("remote", "msg", msg)
}
if result.Pass {
log.Info("PASS")
} else {
log.Error("FAIL")
os.Exit(1)
}
return nil
}
+5 -2
View File
@@ -10,13 +10,14 @@ set(CMAKE_CXX_STANDARD 23)
pico_sdk_init()
set(LIB_SOURCES
lib/dhcp.cpp
lib/dispatch.cpp
lib/handlers.cpp
lib/net.cpp
lib/tusb_config.cpp
w6300/w6300.cpp
)
set(LIB_DEPS pico_stdlib pico_rand tinyusb_device tinyusb_board hardware_pio hardware_spi hardware_dma hardware_clocks)
set(LIB_DEPS pico_stdlib tinyusb_device tinyusb_board hardware_pio hardware_spi hardware_dma hardware_clocks)
add_executable(picomap firmware.cpp ${LIB_SOURCES})
target_include_directories(picomap PRIVATE include w6300)
@@ -24,6 +25,7 @@ target_compile_options(picomap PRIVATE -Wall -Wextra -Wno-unused-parameter)
pico_generate_pio_header(picomap ${CMAKE_CURRENT_LIST_DIR}/w6300/qspi.pio)
pico_enable_stdio_usb(picomap 0)
pico_enable_stdio_uart(picomap 0)
pico_set_binary_type(picomap copy_to_ram)
pico_add_extra_outputs(picomap)
target_link_libraries(picomap ${LIB_DEPS})
@@ -33,5 +35,6 @@ target_compile_options(picomap_test PRIVATE -Wall -Wextra -Wno-unused-parameter)
pico_generate_pio_header(picomap_test ${CMAKE_CURRENT_LIST_DIR}/w6300/qspi.pio)
pico_enable_stdio_usb(picomap_test 0)
pico_enable_stdio_uart(picomap_test 0)
pico_set_binary_type(picomap_test copy_to_ram)
pico_add_extra_outputs(picomap_test)
target_link_libraries(picomap_test ${LIB_DEPS})
+11 -62
View File
@@ -1,66 +1,15 @@
#include "pico/stdlib.h"
#include "pico/bootrom.h"
#include "pico/unique_id.h"
#include "tusb.h"
#include "wire.h"
#include "usb_cdc.h"
#include "timer_queue.h"
#include "dhcp.h"
#include "net.h"
#include "w6300.h"
#include "dispatch.h"
#include "handlers.h"
static usb_cdc usb;
static timer_queue timers;
std::string_view firmware_name = "picomap";
static constexpr handler_entry handlers[] = {
{RequestPICOBOOT::ext_id, handle_picoboot},
{RequestInfo::ext_id, handle_info},
{RequestLog::ext_id, handle_log},
};
int main() {
tusb_init();
net_init();
auto ninfo = w6300::get_net_info();
dhcp_start(timers, ninfo.mac);
static static_vector<uint8_t, 256> rx_buf;
while (true) {
tud_task();
usb.drain();
timers.run();
while (tud_cdc_available()) {
uint8_t byte;
if (tud_cdc_read(&byte, 1) != 1) break;
rx_buf.push_back(byte);
auto msg = try_decode(rx_buf);
if (!msg) {
if (rx_buf.full()) rx_buf.clear();
continue;
}
rx_buf.clear();
switch (msg->type_id) {
case RequestPICOBOOT::ext_id:
usb.send(encode_response(msg->message_id, ResponsePICOBOOT{}));
sleep_ms(100);
reset_usb_boot(0, 1);
break;
case RequestInfo::ext_id: {
ResponseInfo resp;
pico_unique_board_id_t uid;
pico_get_unique_board_id(&uid);
std::copy(uid.id, uid.id + 8, resp.board_id.begin());
auto ninfo = w6300::get_net_info();
resp.mac = ninfo.mac;
usb.send(encode_response(msg->message_id, resp));
break;
}
}
}
__wfi();
}
dispatch_init();
dispatch_run(handlers);
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <functional>
#include <string>
#include <string_view>
#include <vector>
#include "pico/time.h"
#include "ring_buffer.h"
struct log_entry {
uint32_t timestamp_us;
std::string message;
};
inline ring_buffer<log_entry, 32> g_debug_log;
inline void dlog(std::string_view msg) {
g_debug_log.push_overwrite(log_entry{static_cast<uint32_t>(time_us_32()), std::string(msg)});
}
inline void dlog_if_slow(std::string_view label, uint32_t threshold_us, std::function<void()> fn) {
uint32_t t0 = time_us_32();
fn();
uint32_t elapsed = time_us_32() - t0;
if (elapsed > threshold_us)
dlog(std::string(label) + " " + std::to_string(elapsed) + "us");
}
inline std::vector<log_entry> dlog_drain() {
std::vector<log_entry> result;
uint16_t n = g_debug_log.used();
result.reserve(n);
for (uint16_t i = 0; i < n; i++) {
log_entry e;
g_debug_log.peek(std::span{&e, 1});
result.push_back(std::move(e));
g_debug_log.consume(1);
}
return result;
}
-5
View File
@@ -1,5 +0,0 @@
#pragma once
#include <array>
#include "timer_queue.h"
void dhcp_start(timer_queue& timers, const std::array<uint8_t, 6>& mac);
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include <functional>
#include <span>
#include "wire.h"
using handler_fn = size_t (*)(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
struct handler_entry {
int8_t type_id;
handler_fn handle;
};
template <typename Req, auto Fn>
size_t typed_handler(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out) {
msgpack::parser p(payload.data(), static_cast<int>(payload.size()));
Req req;
auto tup = req.as_tuple();
auto r = msgpack::unpack(p, tup);
if (!r) {
return encode_response_into(out, message_id, DeviceError{1, "decode request ext_id=" +
std::to_string(Req::ext_id) + ": msgpack error " + std::to_string(static_cast<int>(r.error()))});
}
return Fn(message_id, req, out);
}
void dispatch_init();
void dispatch_schedule_ms(uint32_t ms, std::function<void()> fn);
[[noreturn]] void dispatch_run(std::span<const handler_entry> handlers);
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <cstdint>
#include <span>
#include <string_view>
#include "wire.h"
extern std::string_view firmware_name;
size_t handle_picoboot(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
size_t handle_info(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
size_t handle_log(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
+78 -53
View File
@@ -6,11 +6,11 @@
#include <expected>
#include <iterator>
#include <limits>
#include <memory>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>
#include "span_writer.h"
namespace msgpack {
@@ -163,26 +163,23 @@ inline result<body_info> get_body_info(const uint8_t *p, int size) {
}
class packer {
public:
using buffer = std::vector<std::uint8_t>;
private:
std::shared_ptr<buffer> m_buffer;
span_writer m_buf;
template <typename T> void push_big_endian(T n) {
auto p = reinterpret_cast<std::uint8_t *>(&n) + (sizeof(T) - 1);
for (size_t i = 0; i < sizeof(T); ++i, --p) {
m_buffer->push_back(*p);
m_buf.push_back(*p);
}
}
template <class Range> void push(const Range &r) {
m_buffer->insert(m_buffer->end(), std::begin(r), std::end(r));
m_buf.insert(m_buf.end(), std::begin(r), std::end(r));
}
public:
packer() : m_buffer(std::make_shared<buffer>()) {}
packer(const std::shared_ptr<buffer> &buf) : m_buffer(buf) {}
packer(uint8_t *data, size_t capacity) : m_buf(data, capacity) {}
packer(span_writer buf) : m_buf(buf) {}
packer(const packer &) = delete;
packer &operator=(const packer &) = delete;
@@ -190,12 +187,12 @@ public:
using pack_result = result<std::reference_wrapper<packer>>;
pack_result pack_nil() {
m_buffer->push_back(format::NIL);
m_buf.push_back(format::NIL);
return *this;
}
pack_result pack_bool(bool v) {
m_buffer->push_back(v ? format::TRUE : format::FALSE);
m_buf.push_back(v ? format::TRUE : format::FALSE);
return *this;
}
@@ -203,36 +200,36 @@ public:
pack_result pack_integer(T n) {
if constexpr (std::is_signed_v<T>) {
if (n >= 0 && n <= 0x7F) {
m_buffer->push_back(static_cast<uint8_t>(n));
m_buf.push_back(static_cast<uint8_t>(n));
} else if (n >= -32 && n < 0) {
m_buffer->push_back(static_cast<uint8_t>(n)); // negative fixint
m_buf.push_back(static_cast<uint8_t>(n)); // negative fixint
} else if (n >= std::numeric_limits<int8_t>::min() && n <= std::numeric_limits<int8_t>::max()) {
m_buffer->push_back(format::INT8);
m_buffer->push_back(static_cast<uint8_t>(n));
m_buf.push_back(format::INT8);
m_buf.push_back(static_cast<uint8_t>(n));
} else if (n >= std::numeric_limits<int16_t>::min() && n <= std::numeric_limits<int16_t>::max()) {
m_buffer->push_back(format::INT16);
m_buf.push_back(format::INT16);
push_big_endian(static_cast<int16_t>(n));
} else if (n >= std::numeric_limits<int32_t>::min() && n <= std::numeric_limits<int32_t>::max()) {
m_buffer->push_back(format::INT32);
m_buf.push_back(format::INT32);
push_big_endian(static_cast<int32_t>(n));
} else {
m_buffer->push_back(format::INT64);
m_buf.push_back(format::INT64);
push_big_endian(static_cast<int64_t>(n));
}
} else {
if (n <= 0x7F) {
m_buffer->push_back(static_cast<uint8_t>(n));
m_buf.push_back(static_cast<uint8_t>(n));
} else if (n <= std::numeric_limits<uint8_t>::max()) {
m_buffer->push_back(format::UINT8);
m_buffer->push_back(static_cast<uint8_t>(n));
m_buf.push_back(format::UINT8);
m_buf.push_back(static_cast<uint8_t>(n));
} else if (n <= std::numeric_limits<uint16_t>::max()) {
m_buffer->push_back(format::UINT16);
m_buf.push_back(format::UINT16);
push_big_endian(static_cast<uint16_t>(n));
} else if (n <= std::numeric_limits<uint32_t>::max()) {
m_buffer->push_back(format::UINT32);
m_buf.push_back(format::UINT32);
push_big_endian(static_cast<uint32_t>(n));
} else {
m_buffer->push_back(format::UINT64);
m_buf.push_back(format::UINT64);
push_big_endian(static_cast<uint64_t>(n));
}
}
@@ -240,13 +237,13 @@ public:
}
pack_result pack_float(float n) {
m_buffer->push_back(format::FLOAT32);
m_buf.push_back(format::FLOAT32);
push_big_endian(n);
return *this;
}
pack_result pack_double(double n) {
m_buffer->push_back(format::FLOAT64);
m_buf.push_back(format::FLOAT64);
push_big_endian(n);
return *this;
}
@@ -255,15 +252,15 @@ public:
pack_result pack_str(const Range &r) {
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
if (sz < 32) {
m_buffer->push_back(format::FIXSTR_MIN | static_cast<uint8_t>(sz));
m_buf.push_back(format::FIXSTR_MIN | static_cast<uint8_t>(sz));
} else if (sz <= std::numeric_limits<uint8_t>::max()) {
m_buffer->push_back(format::STR8);
m_buffer->push_back(static_cast<uint8_t>(sz));
m_buf.push_back(format::STR8);
m_buf.push_back(static_cast<uint8_t>(sz));
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
m_buffer->push_back(format::STR16);
m_buf.push_back(format::STR16);
push_big_endian(static_cast<uint16_t>(sz));
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
m_buffer->push_back(format::STR32);
m_buf.push_back(format::STR32);
push_big_endian(static_cast<uint32_t>(sz));
} else {
return std::unexpected(error_code::overflow);
@@ -280,13 +277,13 @@ public:
pack_result pack_bin(const Range &r) {
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
if (sz <= std::numeric_limits<uint8_t>::max()) {
m_buffer->push_back(format::BIN8);
m_buffer->push_back(static_cast<uint8_t>(sz));
m_buf.push_back(format::BIN8);
m_buf.push_back(static_cast<uint8_t>(sz));
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
m_buffer->push_back(format::BIN16);
m_buf.push_back(format::BIN16);
push_big_endian(static_cast<uint16_t>(sz));
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
m_buffer->push_back(format::BIN32);
m_buf.push_back(format::BIN32);
push_big_endian(static_cast<uint32_t>(sz));
} else {
return std::unexpected(error_code::overflow);
@@ -297,12 +294,12 @@ public:
pack_result pack_array(size_t n) {
if (n <= 15) {
m_buffer->push_back(format::FIXARRAY_MIN | static_cast<uint8_t>(n));
m_buf.push_back(format::FIXARRAY_MIN | static_cast<uint8_t>(n));
} else if (n <= std::numeric_limits<uint16_t>::max()) {
m_buffer->push_back(format::ARRAY16);
m_buf.push_back(format::ARRAY16);
push_big_endian(static_cast<uint16_t>(n));
} else if (n <= std::numeric_limits<uint32_t>::max()) {
m_buffer->push_back(format::ARRAY32);
m_buf.push_back(format::ARRAY32);
push_big_endian(static_cast<uint32_t>(n));
} else {
return std::unexpected(error_code::overflow);
@@ -312,12 +309,12 @@ public:
pack_result pack_map(size_t n) {
if (n <= 15) {
m_buffer->push_back(format::FIXMAP_MIN | static_cast<uint8_t>(n));
m_buf.push_back(format::FIXMAP_MIN | static_cast<uint8_t>(n));
} else if (n <= std::numeric_limits<uint16_t>::max()) {
m_buffer->push_back(format::MAP16);
m_buf.push_back(format::MAP16);
push_big_endian(static_cast<uint16_t>(n));
} else if (n <= std::numeric_limits<uint32_t>::max()) {
m_buffer->push_back(format::MAP32);
m_buf.push_back(format::MAP32);
push_big_endian(static_cast<uint32_t>(n));
} else {
return std::unexpected(error_code::overflow);
@@ -330,26 +327,26 @@ public:
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
switch (sz) {
case 1: m_buffer->push_back(format::FIXEXT1); break;
case 2: m_buffer->push_back(format::FIXEXT2); break;
case 4: m_buffer->push_back(format::FIXEXT4); break;
case 8: m_buffer->push_back(format::FIXEXT8); break;
case 16: m_buffer->push_back(format::FIXEXT16); break;
case 1: m_buf.push_back(format::FIXEXT1); break;
case 2: m_buf.push_back(format::FIXEXT2); break;
case 4: m_buf.push_back(format::FIXEXT4); break;
case 8: m_buf.push_back(format::FIXEXT8); break;
case 16: m_buf.push_back(format::FIXEXT16); break;
default:
if (sz <= std::numeric_limits<uint8_t>::max()) {
m_buffer->push_back(format::EXT8);
m_buffer->push_back(static_cast<uint8_t>(sz));
m_buf.push_back(format::EXT8);
m_buf.push_back(static_cast<uint8_t>(sz));
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
m_buffer->push_back(format::EXT16);
m_buf.push_back(format::EXT16);
push_big_endian(static_cast<uint16_t>(sz));
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
m_buffer->push_back(format::EXT32);
m_buf.push_back(format::EXT32);
push_big_endian(static_cast<uint32_t>(sz));
} else {
return std::unexpected(error_code::overflow);
}
}
m_buffer->push_back(static_cast<uint8_t>(type));
m_buf.push_back(static_cast<uint8_t>(type));
push(r);
return *this;
}
@@ -367,6 +364,18 @@ public:
pack_result pack(const std::vector<uint8_t> &v) { return pack_bin(v); }
template <typename T>
requires (!std::is_same_v<T, uint8_t>)
pack_result pack(const std::vector<T> &v) {
auto r = pack_array(v.size());
if (!r) return r;
for (auto& elem : v) {
r = r->get().pack(elem);
if (!r) return r;
}
return r;
}
template <size_t N>
pack_result pack(const std::array<uint8_t, N> &v) { return pack_bin(v); }
@@ -380,7 +389,8 @@ public:
template <typename T>
requires requires(const T &v) { { T::ext_id } -> std::convertible_to<int8_t>; v.as_tuple(); }
pack_result pack(const T &v) {
packer inner;
uint8_t ext_buf[256];
packer inner(ext_buf, sizeof(ext_buf));
auto r = inner.pack(v.as_tuple());
if (!r) return r;
return pack_ext(T::ext_id, inner.get_payload());
@@ -401,7 +411,7 @@ private:
}
public:
const buffer &get_payload() const { return *m_buffer; }
const span_writer &get_payload() const { return m_buf; }
};
class parser {
@@ -744,6 +754,21 @@ inline result<parser> unpack(const parser &p, std::vector<uint8_t> &out) {
return p.next();
}
template <typename T>
requires (!std::is_same_v<T, uint8_t>)
result<parser> unpack(const parser &p, std::vector<T> &out) {
auto cnt = p.count();
if (!cnt) return std::unexpected(cnt.error());
out.resize(*cnt);
result<parser> cur = p.first_item();
for (size_t i = 0; i < *cnt; i++) {
if (!cur) return cur;
cur = unpack(*cur, out[i]);
}
if (!cur) return cur;
return p.next();
}
template <typename... Ts, size_t... Is>
result<parser> unpack_tuple_elements(const parser &p, std::tuple<Ts...> &t, std::index_sequence<Is...>) {
result<parser> cur = p.first_item();
+15
View File
@@ -1,3 +1,18 @@
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <span>
#include "span_writer.h"
struct net_state {
std::array<uint8_t, 6> mac;
std::array<uint8_t, 4> ip;
};
using net_handler = std::function<size_t(std::span<const uint8_t> payload, span_writer &out)>;
bool net_init();
const net_state& net_get_state();
void net_set_handler(net_handler handler);
void net_poll();
+17 -7
View File
@@ -3,9 +3,9 @@
#include <cstdint>
#include <span>
template <uint16_t N>
template <typename T, uint16_t N>
struct ring_buffer {
std::array<uint8_t, N> data = {};
std::array<T, N> data = {};
uint16_t head = 0;
uint16_t tail = 0;
@@ -13,13 +13,23 @@ struct ring_buffer {
uint16_t free() const { return N - used(); }
bool empty() const { return head == tail; }
void push(std::span<const uint8_t> src) {
void push(std::span<const T> src) {
if (src.size() > free()) return;
for (auto b : src)
data[(tail++) % N] = b;
for (auto& v : src)
data[(tail++) % N] = v;
}
uint16_t peek(std::span<uint8_t> dst) const {
void push(const T& v) {
if (free() == 0) return;
data[(tail++) % N] = v;
}
void push_overwrite(const T& v) {
if (free() == 0) head++;
data[(tail++) % N] = v;
}
uint16_t peek(std::span<T> dst) const {
uint16_t len = dst.size() < used() ? dst.size() : used();
for (uint16_t i = 0; i < len; i++)
dst[i] = data[(head + i) % N];
@@ -34,7 +44,7 @@ struct ring_buffer {
}
}
std::span<const uint8_t> read_contiguous() const {
std::span<const T> read_contiguous() const {
uint16_t offset = head % N;
uint16_t contig = N - offset;
uint16_t pending = used();
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <cstring>
class span_writer {
uint8_t *m_data;
size_t m_capacity;
size_t m_size = 0;
public:
span_writer(uint8_t *data, size_t capacity) : m_data(data), m_capacity(capacity) {}
void push_back(uint8_t v) {
if (m_size < m_capacity) m_data[m_size++] = v;
}
template <class It>
void insert(uint8_t *, It first, It last) {
while (first != last && m_size < m_capacity)
m_data[m_size++] = *first++;
}
size_t size() const { return m_size; }
size_t capacity() const { return m_capacity; }
bool full() const { return m_size >= m_capacity; }
uint8_t *data() { return m_data; }
const uint8_t *data() const { return m_data; }
uint8_t *begin() { return m_data; }
uint8_t *end() { return m_data + m_size; }
const uint8_t *begin() const { return m_data; }
const uint8_t *end() const { return m_data + m_size; }
};
+8 -2
View File
@@ -15,6 +15,7 @@ inline bool operator<(const timer_entry& a, const timer_entry& b) {
struct timer_queue {
sorted_list<timer_entry, 16> queue;
alarm_id_t alarm = -1;
volatile bool irq_pending = false;
void schedule(absolute_time_t when, std::function<void()> fn) {
queue.insert({when, std::move(fn)});
@@ -26,6 +27,8 @@ struct timer_queue {
}
void run() {
if (!irq_pending) return;
irq_pending = false;
while (!queue.empty()) {
auto& front = queue.front();
if (absolute_time_diff_us(get_absolute_time(), front.when) > 0) break;
@@ -39,12 +42,15 @@ struct timer_queue {
bool empty() const { return queue.empty(); }
private:
static int64_t alarm_cb(alarm_id_t, void*) { return 0; }
static int64_t alarm_cb(alarm_id_t, void* user_data) {
static_cast<timer_queue*>(user_data)->irq_pending = true;
return 0;
}
void arm() {
if (alarm >= 0) cancel_alarm(alarm);
alarm = -1;
if (!queue.empty())
alarm = add_alarm_at(queue.front().when, alarm_cb, nullptr, false);
alarm = add_alarm_at(queue.front().when, alarm_cb, this, false);
}
};
+1 -1
View File
@@ -6,7 +6,7 @@
#include "ring_buffer.h"
struct usb_cdc {
ring_buffer<512> tx;
ring_buffer<uint8_t, 8192> tx;
void send(std::span<const uint8_t> data) {
tx.push(data);
+84 -11
View File
@@ -1,6 +1,7 @@
#pragma once
#include <array>
#include <cstdint>
#include <span>
#include <string>
#include <tuple>
#include <vector>
@@ -47,8 +48,45 @@ struct ResponseInfo {
static constexpr int8_t ext_id = 5;
std::array<uint8_t, 8> board_id;
std::array<uint8_t, 6> mac;
auto as_tuple() const { return std::tie(board_id, mac); }
auto as_tuple() { return std::tie(board_id, mac); }
std::array<uint8_t, 4> ip;
std::string firmware_name;
auto as_tuple() const { return std::tie(board_id, mac, ip, firmware_name); }
auto as_tuple() { return std::tie(board_id, mac, ip, firmware_name); }
};
struct RequestLog {
static constexpr int8_t ext_id = 6;
auto as_tuple() const { return std::tie(); }
auto as_tuple() { return std::tie(); }
};
struct LogEntry {
uint32_t timestamp_us;
std::string message;
auto as_tuple() const { return std::tie(timestamp_us, message); }
auto as_tuple() { return std::tie(timestamp_us, message); }
};
struct ResponseLog {
static constexpr int8_t ext_id = 7;
std::vector<LogEntry> entries;
auto as_tuple() const { return std::tie(entries); }
auto as_tuple() { return std::tie(entries); }
};
struct RequestTest {
static constexpr int8_t ext_id = 127;
std::string name;
auto as_tuple() const { return std::tie(name); }
auto as_tuple() { return std::tie(name); }
};
struct ResponseTest {
static constexpr int8_t ext_id = 126;
bool pass;
std::vector<std::string> messages;
auto as_tuple() const { return std::tie(pass, messages); }
auto as_tuple() { return std::tie(pass, messages); }
};
static constexpr uint8_t hash_key[8] = {};
@@ -56,20 +94,30 @@ static constexpr uint8_t hash_key[8] = {};
struct DecodedMessage {
uint32_t message_id;
int8_t type_id;
std::vector<uint8_t> payload;
};
inline std::vector<uint8_t> pack_envelope(uint32_t message_id, const std::vector<uint8_t> &payload) {
uint32_t checksum = halfsiphash::hash32(payload.data(), payload.size(), hash_key);
msgpack::packer p;
p.pack(Envelope{message_id, checksum, payload});
return p.get_payload();
inline size_t pack_envelope_into(span_writer &out, uint32_t message_id, const uint8_t *payload, size_t payload_len) {
uint32_t checksum = halfsiphash::hash32(payload, payload_len, hash_key);
uint8_t env_buf[512];
span_writer env_body(env_buf, sizeof(env_buf));
msgpack::packer env_p(env_body);
env_p.pack_array(3);
env_p.pack(message_id);
env_p.pack(checksum);
env_p.pack_bin(std::span<const uint8_t>{payload, payload_len});
msgpack::packer outer(out);
outer.pack_ext(Envelope::ext_id, env_body);
return out.size();
}
template <typename T>
inline std::vector<uint8_t> encode_response(uint32_t message_id, const T &msg) {
msgpack::packer inner;
inline size_t encode_response_into(span_writer &out, uint32_t message_id, const T &msg) {
uint8_t inner_buf[256];
msgpack::packer inner(inner_buf, sizeof(inner_buf));
inner.pack(msg);
return pack_envelope(message_id, inner.get_payload());
auto &pl = inner.get_payload();
return pack_envelope_into(out, message_id, pl.data(), pl.size());
}
inline msgpack::result<DecodedMessage> try_decode(const uint8_t *data, size_t len) {
@@ -87,10 +135,35 @@ inline msgpack::result<DecodedMessage> try_decode(const uint8_t *data, size_t le
auto ext = inner.get_ext();
if (!ext) return std::unexpected(ext.error());
return DecodedMessage{env.message_id, std::get<0>(*ext)};
auto& [type_id, ext_data] = *ext;
return DecodedMessage{env.message_id, type_id,
std::vector<uint8_t>(reinterpret_cast<const uint8_t*>(ext_data.data()),
reinterpret_cast<const uint8_t*>(ext_data.data()) + ext_data.size())};
}
template <size_t N>
inline msgpack::result<DecodedMessage> try_decode(const static_vector<uint8_t, N> &buf) {
return try_decode(buf.data(), buf.size());
}
template <typename T>
inline msgpack::result<T> decode_response(const uint8_t *data, size_t len) {
msgpack::parser p(data, static_cast<int>(len));
Envelope env;
auto r = msgpack::unpack(p, env);
if (!r) return std::unexpected(r.error());
uint32_t expected = halfsiphash::hash32(env.payload.data(), env.payload.size(), hash_key);
if (env.checksum != expected) return std::unexpected(msgpack::error_code::invalid);
msgpack::parser inner(env.payload.data(), static_cast<int>(env.payload.size()));
T out;
auto r2 = msgpack::unpack(inner, out);
if (!r2) return std::unexpected(r2.error());
return out;
}
inline size_t encode_request_into(span_writer &out, uint32_t message_id, const auto &msg) {
return encode_response_into(out, message_id, msg);
}
-55
View File
@@ -1,55 +0,0 @@
#include "dhcp.h"
#include <span>
#include "pico/rand.h"
#include "w6300.h"
namespace dhcp_opt {
constexpr uint8_t message_type = 53;
constexpr uint8_t param_request = 55;
constexpr uint8_t end = 255;
constexpr uint8_t discover = 1;
constexpr uint8_t subnet_mask = 1;
}
struct __attribute__((packed)) dhcp_discover {
uint8_t op = 1;
uint8_t htype = 1;
uint8_t hlen = 6;
uint8_t hops = 0;
uint32_t xid = __builtin_bswap32(static_cast<uint32_t>(get_rand_64()));
uint16_t secs = 0;
uint16_t flags = __builtin_bswap16(0x8000);
std::array<uint8_t, 4> ciaddr = {};
std::array<uint8_t, 4> yiaddr = {};
std::array<uint8_t, 4> siaddr = {};
std::array<uint8_t, 4> giaddr = {};
std::array<uint8_t, 16> chaddr = {};
std::array<uint8_t, 64> sname = {};
std::array<uint8_t, 128> file = {};
std::array<uint8_t, 4> magic = {99, 130, 83, 99};
uint8_t opt_msg_type[3] = {dhcp_opt::message_type, 1, dhcp_opt::discover};
uint8_t opt_params[3] = {dhcp_opt::param_request, 1, dhcp_opt::subnet_mask};
uint8_t opt_end = dhcp_opt::end;
};
static_assert(sizeof(dhcp_discover) == 247);
static void send_discover(timer_queue& timers, const std::array<uint8_t, 6>& mac) {
auto sn = w6300::socket_id{0};
w6300::open_socket(sn, w6300::protocol::udp, w6300::port_num{68}, w6300::sock_flag::none);
dhcp_discover pkt;
std::copy(mac.begin(), mac.end(), pkt.chaddr.begin());
w6300::ip_address broadcast = {};
broadcast.ip = {255, 255, 255, 255};
broadcast.len = 4;
w6300::sendto(sn, std::span{reinterpret_cast<uint8_t*>(&pkt), sizeof(pkt)}, broadcast, w6300::port_num{67});
timers.schedule_ms(5000, [&timers, mac]() { send_discover(timers, mac); });
}
void dhcp_start(timer_queue& timers, const std::array<uint8_t, 6>& mac) {
send_discover(timers, mac);
}
+84
View File
@@ -0,0 +1,84 @@
#include "dispatch.h"
#include <unordered_map>
#include "pico/stdlib.h"
#include "tusb.h"
#include "wire.h"
#include "usb_cdc.h"
#include "timer_queue.h"
#include "net.h"
#include "debug_log.h"
#include "hardware/sync.h"
static timer_queue timers;
void dispatch_init() {
tusb_init();
net_init();
dlog("dispatch_init complete");
}
void dispatch_schedule_ms(uint32_t ms, std::function<void()> fn) {
timers.schedule_ms(ms, std::move(fn));
}
[[noreturn]] void dispatch_run(std::span<const handler_entry> handlers) {
std::unordered_map<int8_t, handler_fn> handler_map;
for (auto& entry : handlers) {
handler_map[entry.type_id] = entry.handle;
}
static usb_cdc usb;
static static_vector<uint8_t, 256> usb_rx_buf;
static uint8_t tx_buf[1514];
net_set_handler([&](std::span<const uint8_t> payload, span_writer &out) -> size_t {
auto msg = try_decode(payload.data(), payload.size());
if (!msg) return 0;
auto it = handler_map.find(msg->type_id);
if (it == handler_map.end()) return 0;
return it->second(msg->message_id, msg->payload, out);
});
while (true) {
uint32_t save = save_and_disable_interrupts();
dlog_if_slow("tud_task", 1000, [&]{ tud_task(); });
dlog_if_slow("drain", 1000, [&]{ usb.drain(); });
dlog_if_slow("timers", 1000, [&]{ timers.run(); });
dlog_if_slow("net_poll", 1000, [&]{ net_poll(); });
while (tud_cdc_available()) {
uint8_t byte;
if (tud_cdc_read(&byte, 1) != 1) break;
usb_rx_buf.push_back(byte);
auto msg = try_decode(usb_rx_buf);
if (!msg) {
if (usb_rx_buf.full()) usb_rx_buf.clear();
continue;
}
usb_rx_buf.clear();
auto it = handler_map.find(msg->type_id);
if (it != handler_map.end()) {
span_writer out(tx_buf, sizeof(tx_buf));
size_t resp_len = it->second(msg->message_id, msg->payload, out);
if (resp_len > 0) {
if (resp_len > usb.tx.free()) {
span_writer err_out(tx_buf, sizeof(tx_buf));
size_t err_len = encode_response_into(err_out, msg->message_id,
DeviceError{2, "response too large: " + std::to_string(resp_len)});
usb.send(std::span<const uint8_t>{tx_buf, err_len});
} else {
usb.send(std::span<const uint8_t>{tx_buf, resp_len});
}
}
}
}
__wfi();
restore_interrupts(save);
}
}
+30
View File
@@ -0,0 +1,30 @@
#include "handlers.h"
#include "pico/unique_id.h"
#include "pico/bootrom.h"
#include "dispatch.h"
#include "net.h"
#include "debug_log.h"
size_t handle_picoboot(uint32_t message_id, std::span<const uint8_t>, span_writer &out) {
dispatch_schedule_ms(100, []{ reset_usb_boot(0, 1); });
return encode_response_into(out, message_id, ResponsePICOBOOT{});
}
size_t handle_info(uint32_t message_id, std::span<const uint8_t>, span_writer &out) {
ResponseInfo resp;
pico_unique_board_id_t uid;
pico_get_unique_board_id(&uid);
std::copy(uid.id, uid.id + 8, resp.board_id.begin());
auto& ns = net_get_state();
resp.mac = ns.mac;
resp.ip = ns.ip;
resp.firmware_name = firmware_name;
return encode_response_into(out, message_id, resp);
}
size_t handle_log(uint32_t message_id, std::span<const uint8_t>, span_writer &out) {
ResponseLog resp;
for (auto& e : dlog_drain())
resp.entries.push_back(LogEntry{e.timestamp_us, std::move(e.message)});
return encode_response_into(out, message_id, resp);
}
+286 -9
View File
@@ -1,24 +1,301 @@
#include "net.h"
#include <cstring>
#include "pico/unique_id.h"
#include "pico/time.h"
#include "w6300.h"
#include "debug_log.h"
using mac_addr = std::array<uint8_t, 6>;
using ip4_addr = std::array<uint8_t, 4>;
struct __attribute__((packed)) eth_header {
mac_addr dst;
mac_addr src;
uint16_t ethertype;
};
static_assert(sizeof(eth_header) == 14);
struct __attribute__((packed)) arp_packet {
eth_header eth;
uint16_t htype;
uint16_t ptype;
uint8_t hlen;
uint8_t plen;
uint16_t oper;
mac_addr sha;
ip4_addr spa;
mac_addr tha;
ip4_addr tpa;
};
static_assert(sizeof(arp_packet) == 42);
struct __attribute__((packed)) ipv4_header {
eth_header eth;
uint8_t ver_ihl;
uint8_t dscp_ecn;
uint16_t total_len;
uint16_t identification;
uint16_t flags_frag;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
ip4_addr src;
ip4_addr dst;
size_t ip_header_len() const { return (ver_ihl & 0x0F) * 4; }
size_t ip_total_len() const { return __builtin_bswap16(total_len); }
const uint8_t* ip_start() const { return reinterpret_cast<const uint8_t*>(&ver_ihl); }
uint8_t* ip_start() { return reinterpret_cast<uint8_t*>(&ver_ihl); }
};
static_assert(sizeof(ipv4_header) == 34);
struct __attribute__((packed)) udp_header {
ipv4_header ip;
uint16_t src_port;
uint16_t dst_port;
uint16_t length;
uint16_t checksum;
};
static_assert(sizeof(udp_header) == 42);
struct __attribute__((packed)) icmp_echo {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint16_t id;
uint16_t seq;
};
static_assert(sizeof(icmp_echo) == 8);
static constexpr uint16_t ETH_ARP = __builtin_bswap16(0x0806);
static constexpr uint16_t ETH_IPV4 = __builtin_bswap16(0x0800);
static constexpr uint16_t ARP_HTYPE_ETH = __builtin_bswap16(1);
static constexpr uint16_t ARP_PTYPE_IPV4 = __builtin_bswap16(0x0800);
static constexpr uint16_t ARP_OP_REQUEST = __builtin_bswap16(1);
static constexpr uint16_t ARP_OP_REPLY = __builtin_bswap16(2);
static constexpr uint16_t PICOMAP_PORT = __builtin_bswap16(28781);
static constexpr mac_addr MAC_BROADCAST = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
static constexpr ip4_addr IP_BROADCAST_ALL = {255, 255, 255, 255};
static constexpr ip4_addr IP_BROADCAST_SUBNET = {169, 254, 255, 255};
static net_state state;
static w6300::socket_id raw_socket{0};
static net_handler msg_handler;
static uint16_t ip_checksum(const void* data, size_t len) {
auto p = static_cast<const uint8_t*>(data);
uint32_t sum = 0;
for (size_t i = 0; i < len - 1; i += 2)
sum += (p[i] << 8) | p[i + 1];
if (len & 1)
sum += p[len - 1] << 8;
while (sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
return __builtin_bswap16(~sum);
}
static bool mac_match(const mac_addr& dst) {
return dst == state.mac || dst == MAC_BROADCAST;
}
static bool ip_match(const ip4_addr& dst) {
return dst == state.ip;
}
static bool ip_match_or_broadcast(const ip4_addr& dst) {
return ip_match(dst) || dst == IP_BROADCAST_ALL || dst == IP_BROADCAST_SUBNET;
}
static void send_raw(const void* data, size_t len) {
dlog_if_slow("send_raw", 1000, [&]{
w6300::send(raw_socket, std::span<const uint8_t>{static_cast<const uint8_t*>(data), len});
});
}
static void handle_arp(const uint8_t* frame, size_t len) {
if (len < sizeof(arp_packet)) return;
auto& pkt = *reinterpret_cast<const arp_packet*>(frame);
if (pkt.htype != ARP_HTYPE_ETH) return;
if (pkt.ptype != ARP_PTYPE_IPV4) return;
if (pkt.hlen != 6 || pkt.plen != 4) return;
if (pkt.oper != ARP_OP_REQUEST) return;
if (!ip_match(pkt.tpa)) return;
arp_packet reply = {};
reply.eth.dst = pkt.eth.src;
reply.eth.src = state.mac;
reply.eth.ethertype = ETH_ARP;
reply.htype = ARP_HTYPE_ETH;
reply.ptype = ARP_PTYPE_IPV4;
reply.hlen = 6;
reply.plen = 4;
reply.oper = ARP_OP_REPLY;
reply.sha = state.mac;
reply.spa = state.ip;
reply.tha = pkt.sha;
reply.tpa = pkt.spa;
send_raw(&reply, sizeof(reply));
}
static uint8_t tx_buf[1514];
static void handle_udp(const uint8_t* frame, size_t len) {
if (len < sizeof(udp_header)) return;
auto& pkt = *reinterpret_cast<const udp_header*>(frame);
if (pkt.dst_port != PICOMAP_PORT) return;
if (!ip_match_or_broadcast(pkt.ip.dst)) return;
if (!msg_handler) return;
size_t udp_len = __builtin_bswap16(pkt.length);
if (udp_len < 8) return;
if (sizeof(eth_header) + pkt.ip.ip_total_len() < sizeof(udp_header) + udp_len - 8) return;
auto* payload = frame + sizeof(udp_header);
size_t payload_len = udp_len - 8;
span_writer resp(tx_buf + sizeof(udp_header), sizeof(tx_buf) - sizeof(udp_header));
size_t resp_len = msg_handler(std::span<const uint8_t>{payload, payload_len}, resp);
if (resp_len == 0) return;
size_t ip_total = 20 + 8 + resp_len;
size_t reply_len = sizeof(eth_header) + ip_total;
auto& rip = *reinterpret_cast<ipv4_header*>(tx_buf);
rip.eth.dst = pkt.ip.eth.src;
rip.eth.src = state.mac;
rip.eth.ethertype = ETH_IPV4;
rip.ver_ihl = 0x45;
rip.dscp_ecn = 0;
rip.total_len = __builtin_bswap16(ip_total);
rip.identification = 0;
rip.flags_frag = 0;
rip.ttl = 64;
rip.protocol = 17;
rip.checksum = 0;
rip.src = state.ip;
rip.dst = pkt.ip.src;
rip.checksum = ip_checksum(rip.ip_start(), 20);
auto& rudp = *reinterpret_cast<udp_header*>(tx_buf);
rudp.src_port = PICOMAP_PORT;
rudp.dst_port = pkt.src_port;
rudp.length = __builtin_bswap16(8 + resp_len);
rudp.checksum = 0;
send_raw(tx_buf, reply_len);
}
static void handle_icmp(const uint8_t* frame, size_t len) {
auto& ip = *reinterpret_cast<const ipv4_header*>(frame);
size_t ip_hdr_len = ip.ip_header_len();
size_t ip_total = ip.ip_total_len();
if (sizeof(eth_header) + ip_total > len) return;
if (ip.protocol != 1) return;
if (!ip_match_or_broadcast(ip.dst)) return;
auto& icmp = *reinterpret_cast<const icmp_echo*>(frame + sizeof(eth_header) + ip_hdr_len);
size_t icmp_len = ip_total - ip_hdr_len;
if (icmp_len < sizeof(icmp_echo)) return;
if (icmp.type != 8) return;
uint8_t reply_buf[1514];
size_t reply_len = sizeof(eth_header) + ip_total;
if (reply_len > sizeof(reply_buf)) return;
memcpy(reply_buf, frame, reply_len);
auto& rip = *reinterpret_cast<ipv4_header*>(reply_buf);
rip.eth.dst = ip.eth.src;
rip.eth.src = state.mac;
rip.src = state.ip;
rip.dst = ip.src;
rip.ttl = 64;
rip.checksum = 0;
rip.checksum = ip_checksum(rip.ip_start(), ip_hdr_len);
auto& ricmp = *reinterpret_cast<icmp_echo*>(reply_buf + sizeof(eth_header) + ip_hdr_len);
ricmp.type = 0;
ricmp.checksum = 0;
ricmp.checksum = ip_checksum(&ricmp, icmp_len);
send_raw(reply_buf, reply_len);
}
static void handle_ipv4(const uint8_t* frame, size_t len) {
if (len < sizeof(ipv4_header)) return;
auto& ip = *reinterpret_cast<const ipv4_header*>(frame);
if ((ip.ver_ihl >> 4) != 4) return;
switch (ip.protocol) {
case 1:
handle_icmp(frame, len);
break;
case 17:
handle_udp(frame, len);
break;
}
}
static void process_frame(const uint8_t* frame, size_t len) {
if (len < sizeof(eth_header)) return;
auto& eth = *reinterpret_cast<const eth_header*>(frame);
if (!mac_match(eth.dst)) return;
switch (eth.ethertype) {
case ETH_ARP:
handle_arp(frame, len);
break;
case ETH_IPV4:
handle_ipv4(frame, len);
break;
}
}
bool net_init() {
w6300::init_spi();
w6300::init_critical_section();
w6300::reset();
w6300::init();
if (!w6300::check()) return false;
pico_unique_board_id_t uid;
pico_get_unique_board_id(&uid);
w6300::net_info info = {};
info.mac[0] = (uid.id[0] & 0xFC) | 0x02;
info.mac[1] = uid.id[1];
info.mac[2] = uid.id[2];
info.mac[3] = uid.id[3];
info.mac[4] = uid.id[4];
info.mac[5] = uid.id[5];
w6300::init_net(info);
state.mac[0] = (uid.id[0] & 0xFC) | 0x02;
state.mac[1] = uid.id[1];
state.mac[2] = uid.id[2];
state.mac[3] = uid.id[3];
state.mac[4] = uid.id[4];
state.mac[5] = uid.id[5];
state.ip[0] = 169;
state.ip[1] = 254;
state.ip[2] = state.mac[4];
state.ip[3] = state.mac[5];
w6300::open_socket(raw_socket, w6300::protocol::macraw, w6300::sock_flag::none);
w6300::set_interrupt_mask(w6300::ik_sock_0);
return true;
}
const net_state& net_get_state() {
return state;
}
void net_set_handler(net_handler handler) {
msg_handler = std::move(handler);
}
void net_poll() {
if (!w6300::irq_pending) return;
w6300::irq_pending = false;
w6300::clear_interrupt(w6300::ik_int_all);
if (w6300::get_socket_recv_buf(raw_socket) == 0) return;
static uint8_t rx_buf[1518];
auto result = w6300::recv(raw_socket, std::span{rx_buf});
if (!result) return;
process_frame(rx_buf, *result);
}
+114 -50
View File
@@ -1,58 +1,122 @@
#include <unordered_map>
#include "pico/stdlib.h"
#include "pico/bootrom.h"
#include "pico/unique_id.h"
#include "tusb.h"
#include "wire.h"
#include "usb_cdc.h"
#include "net.h"
#include "pico/time.h"
#include "hardware/gpio.h"
#include "dispatch.h"
#include "handlers.h"
#include "w6300.h"
static usb_cdc usb;
static constexpr uint8_t LED_PIN = 25;
static void led_toggle() {
gpio_xor_mask(1 << LED_PIN);
dispatch_schedule_ms(1000, led_toggle);
}
std::string_view firmware_name = "picomap_test";
static constexpr uint16_t PICOMAP_DISCOVERY_PORT = 28777;
static constexpr std::array<uint8_t, 4> picomap_discovery_ip = {239, 0, 112, 109};
static constexpr std::array<uint8_t, 6> picomap_discovery_mac = {
0x01, 0x00, 0x5e, 0x00, 0x70, 0x6d,
};
static w6300::socket_id test_socket{1};
static ResponseTest test_discovery() {
ResponseTest resp;
resp.pass = true;
uint8_t req_buf[256];
span_writer req_out(req_buf, sizeof(req_buf));
size_t req_len = encode_request_into(req_out, 0, RequestInfo{});
auto send_result = w6300::send(test_socket, std::span<const uint8_t>{req_buf, req_len});
if (!send_result) {
resp.pass = false;
resp.messages.push_back("send: error " + std::to_string(static_cast<int>(send_result.error())));
return resp;
}
uint8_t rx_buf[512];
auto deadline = make_timeout_time_ms(5000);
std::expected<uint16_t, w6300::sock_error> recv_result = std::unexpected(w6300::sock_error::busy);
while (get_absolute_time() < deadline) {
recv_result = w6300::recv(test_socket, std::span{rx_buf});
if (recv_result || recv_result.error() != w6300::sock_error::busy) break;
}
if (!recv_result) {
resp.pass = false;
if (recv_result.error() == w6300::sock_error::busy) {
resp.messages.push_back("recv: timed out after 5s");
} else {
resp.messages.push_back("recv: error " + std::to_string(static_cast<int>(recv_result.error())));
}
return resp;
}
resp.messages.push_back("received " + std::to_string(*recv_result) + " bytes");
auto info = decode_response<ResponseInfo>(rx_buf, *recv_result);
if (!info) {
resp.pass = false;
resp.messages.push_back("decode: msgpack error " + std::to_string(static_cast<int>(info.error())));
return resp;
}
if (info->firmware_name.empty()) {
resp.pass = false;
resp.messages.push_back("firmware_name is empty");
} else {
resp.messages.push_back("firmware_name: " + info->firmware_name);
}
bool mac_zero = true;
for (auto b : info->mac) { if (b != 0) { mac_zero = false; break; } }
if (mac_zero) {
resp.pass = false;
resp.messages.push_back("mac is all zeros");
}
bool ip_zero = true;
for (auto b : info->ip) { if (b != 0) { ip_zero = false; break; } }
if (ip_zero) {
resp.pass = false;
resp.messages.push_back("ip is all zeros");
}
return resp;
}
using test_fn = ResponseTest (*)();
static const std::unordered_map<std::string_view, test_fn> tests = {
{"discovery", test_discovery},
};
static size_t handle_test(uint32_t message_id, const RequestTest& req, span_writer &out) {
auto it = tests.find(req.name);
if (it == tests.end())
return encode_response_into(out, message_id, ResponseTest{false, {"unknown test: " + req.name}});
return encode_response_into(out, message_id, it->second());
}
static constexpr handler_entry handlers[] = {
{RequestPICOBOOT::ext_id, handle_picoboot},
{RequestInfo::ext_id, handle_info},
{RequestLog::ext_id, handle_log},
{RequestTest::ext_id, typed_handler<RequestTest, handle_test>},
};
int main() {
tusb_init();
net_init();
dispatch_init();
static static_vector<uint8_t, 256> rx_buf;
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
dispatch_schedule_ms(1000, led_toggle);
while (true) {
tud_task();
usb.drain();
while (tud_cdc_available()) {
uint8_t byte;
if (tud_cdc_read(&byte, 1) != 1) break;
rx_buf.push_back(byte);
auto msg = try_decode(rx_buf);
if (!msg) {
if (rx_buf.full()) rx_buf.clear();
continue;
}
rx_buf.clear();
switch (msg->type_id) {
case RequestPICOBOOT::ext_id:
usb.send(encode_response(msg->message_id, ResponsePICOBOOT{}));
sleep_ms(100);
reset_usb_boot(0, 1);
break;
case RequestInfo::ext_id: {
ResponseInfo resp;
pico_unique_board_id_t uid;
pico_get_unique_board_id(&uid);
std::copy(uid.id, uid.id + 8, resp.board_id.begin());
auto ninfo = w6300::get_net_info();
resp.mac = ninfo.mac;
usb.send(encode_response(msg->message_id, resp));
break;
}
}
}
__wfi();
}
dispatch_run(handlers);
}
+123 -1292
View File
File diff suppressed because it is too large Load Diff
+5 -221
View File
@@ -2,266 +2,50 @@
#include <array>
#include <cstdint>
#include <expected>
#include <optional>
#include <span>
namespace w6300 {
constexpr int SOCK_COUNT = 8;
enum class socket_id : uint8_t {};
enum class port_num : uint16_t {};
enum class sock_error : int16_t {
busy = 0,
sock_num = -1,
sock_opt = -2,
sock_init = -3,
sock_closed = -4,
sock_mode = -5,
sock_flag = -6,
sock_status = -7,
arg = -10,
port_zero = -11,
ip_invalid = -12,
timeout = -13,
data_len = -14,
buffer = -15,
fatal_packlen = -1001,
};
enum class protocol : uint8_t {
tcp = 0x01,
udp = 0x02,
ipraw = 0x03,
macraw = 0x07,
tcp6 = 0x09,
udp6 = 0x0A,
ipraw6 = 0x0B,
tcp_dual = 0x0D,
udp_dual = 0x0E,
};
enum class sock_flag : uint8_t {
none = 0,
multi_enable = 1 << 7,
ether_own = 1 << 7,
broad_block = 1 << 6,
tcp_fpsh = 1 << 6,
tcp_nodelay = 1 << 5,
igmp_ver2 = 1 << 5,
solicit_block = 1 << 5,
ether_multi4b = 1 << 5,
uni_block = 1 << 4,
ether_multi6b = 1 << 4,
force_arp = 1 << 0,
dha_manual = 1 << 1,
io_nonblock = 1 << 3,
};
constexpr sock_flag operator|(sock_flag a, sock_flag b) {
return static_cast<sock_flag>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
}
constexpr uint8_t operator&(sock_flag a, sock_flag b) {
return static_cast<uint8_t>(a) & static_cast<uint8_t>(b);
}
enum class pack_info : uint8_t {
none = 0x00,
first = 1 << 1,
remained = 1 << 2,
completed = 1 << 3,
ipv6_lla = (1 << 7) | (1 << 4),
ipv6_multi = (1 << 7) | (1 << 5),
ipv6_allnode = (1 << 7) | (1 << 6),
ipv6 = 1 << 7,
};
constexpr pack_info operator|(pack_info a, pack_info b) {
return static_cast<pack_info>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
}
constexpr uint8_t operator&(pack_info a, pack_info b) {
return static_cast<uint8_t>(a) & static_cast<uint8_t>(b);
}
enum class srcv6_prefer : uint8_t {
auto_select = 0x00,
lla = 0x02,
gua = 0x03,
};
enum class tcp_sock_info : uint8_t {
mode = 1 << 2,
op = 1 << 1,
sip = 1 << 0,
};
enum class sock_io_mode : uint8_t {
block = 0,
nonblock = 1,
};
enum intr_kind : uint32_t {
IK_PPPOE_TERMINATED = (1 << 0), IK_DEST_UNREACH = (1 << 1), IK_IP_CONFLICT = (1 << 2),
IK_DEST_UNREACH6 = (1 << 4), IK_WOL = (1 << 7), IK_NET_ALL = 0x97,
IK_SOCK_0 = (1 << 8), IK_SOCK_1 = (1 << 9), IK_SOCK_2 = (1 << 10), IK_SOCK_3 = (1 << 11),
IK_SOCK_4 = (1 << 12), IK_SOCK_5 = (1 << 13), IK_SOCK_6 = (1 << 14), IK_SOCK_7 = (1 << 15),
IK_SOCK_ALL = (0xFF << 8),
IK_SOCKL_TOUT = (1 << 16), IK_SOCKL_ARP4 = (1 << 17), IK_SOCKL_PING4 = (1 << 18),
IK_SOCKL_ARP6 = (1 << 19), IK_SOCKL_PING6 = (1 << 20), IK_SOCKL_NS = (1 << 21),
IK_SOCKL_RS = (1 << 22), IK_SOCKL_RA = (1 << 23), IK_SOCKL_ALL = (0xFF << 16),
IK_INT_ALL = 0x00FFFF97
};
struct phy_conf {
uint8_t by;
uint8_t mode;
uint8_t speed;
uint8_t duplex;
};
enum ipconf_mode : uint8_t {
NETINFO_NONE = 0x00, NETINFO_STATIC_V4 = 0x01, NETINFO_STATIC_V6 = 0x02,
NETINFO_STATIC_ALL = 0x03, NETINFO_SLAAC_V6 = 0x04,
NETINFO_DHCP_V4 = 0x10, NETINFO_DHCP_V6 = 0x20, NETINFO_DHCP_ALL = 0x30
};
enum dhcp_mode : uint8_t { NETINFO_STATIC = 1, NETINFO_DHCP };
struct net_info {
std::array<uint8_t, 6> mac;
std::array<uint8_t, 4> ip;
std::array<uint8_t, 4> sn;
std::array<uint8_t, 4> gw;
std::array<uint8_t, 16> lla;
std::array<uint8_t, 16> gua;
std::array<uint8_t, 16> sn6;
std::array<uint8_t, 16> gw6;
std::array<uint8_t, 4> dns;
std::array<uint8_t, 16> dns6;
ipconf_mode ipmode;
dhcp_mode dhcp;
};
enum netmode_type : uint32_t {
NM_IPB_V4 = (1 << 0), NM_IPB_V6 = (1 << 1), NM_WOL = (1 << 2),
NM_PB6_MULTI = (1 << 4), NM_PB6_ALLNODE = (1 << 5), NM_MR_MASK = 0x37,
NM_PPPoE = (1 << 8), NM_DHA_SELECT = (1 << 15), NM_MR2_MASK = (0x09 << 8),
NM_PB4_ALL = (1 << 16), NM_TRSTB_V4 = (1 << 17), NM_PARP_V4 = (1 << 18),
NM_UNRB_V4 = (1 << 19), NM_NET4_MASK = (0x0F << 16),
NM_PB6_ALL = (1 << 24), NM_TRSTB_V6 = (1 << 25), NM_PARP_V6 = (1 << 26),
NM_UNRB_V6 = (1 << 27), NM_NET6_MASK = (0x0F << 24),
NM_MASK_ALL = 0x0F0F0937
};
struct net_timeout {
uint8_t s_retry_cnt;
uint16_t s_time_100us;
uint8_t sl_retry_cnt;
uint16_t sl_time_100us;
};
struct ip_address {
std::array<uint8_t, 16> ip;
uint8_t len;
};
struct prefix {
uint8_t len;
uint8_t flag;
uint32_t valid_lifetime;
uint32_t preferred_lifetime;
std::array<uint8_t, 16> prefix;
};
struct arp_request {
ip_address destinfo;
std::array<uint8_t, 6> dha;
};
struct ping_request {
uint16_t id;
uint16_t seq;
ip_address destinfo;
ik_sock_0 = (1 << 8),
ik_int_all = 0x00FFFF97
};
void init_spi();
void init_critical_section();
void reset();
void init();
bool check();
void init_net(const net_info& info);
void soft_reset();
int8_t init_buffers(std::span<const uint8_t> txsize, std::span<const uint8_t> rxsize);
extern volatile bool irq_pending;
void clear_interrupt(intr_kind intr);
intr_kind get_interrupt();
void set_interrupt_mask(intr_kind intr);
intr_kind get_interrupt_mask();
int8_t get_phy_link();
int8_t get_phy_power_mode();
void reset_phy();
void set_phy_conf(const phy_conf& conf);
phy_conf get_phy_conf();
phy_conf get_phy_status();
void set_phy_power_mode(uint8_t pmode);
void set_net_info(const net_info& info);
net_info get_net_info();
void set_net_mode(netmode_type mode);
netmode_type get_net_mode();
void set_timeout(const net_timeout& timeout);
net_timeout get_timeout();
int8_t send_arp(arp_request& arp);
int8_t send_ping(const ping_request& ping);
int8_t send_dad(std::span<const uint8_t, 16> ipv6);
int8_t send_slaac(prefix& pfx);
int8_t send_unsolicited();
int8_t get_prefix(prefix& pfx);
std::expected<socket_id, sock_error> open_socket(socket_id sn, protocol proto, port_num port, sock_flag flag);
std::expected<void, sock_error> close(socket_id sn);
std::expected<void, sock_error> listen(socket_id sn);
std::expected<void, sock_error> disconnect(socket_id sn);
std::expected<socket_id, sock_error> open_socket(socket_id sn, protocol proto, sock_flag flag);
std::expected<uint16_t, sock_error> send(socket_id sn, std::span<const uint8_t> buf);
std::expected<uint16_t, sock_error> recv(socket_id sn, std::span<uint8_t> buf);
std::expected<void, sock_error> connect(socket_id sn, const ip_address& addr, port_num port);
std::expected<uint16_t, sock_error> sendto(socket_id sn, std::span<const uint8_t> buf, const ip_address& addr, port_num port);
std::expected<uint16_t, sock_error> recvfrom(socket_id sn, std::span<uint8_t> buf, ip_address& addr, port_num& port);
std::expected<void, sock_error> set_socket_io_mode(socket_id sn, sock_io_mode mode);
sock_io_mode get_socket_io_mode(socket_id sn);
uint16_t get_socket_max_tx(socket_id sn);
uint16_t get_socket_max_rx(socket_id sn);
std::expected<void, sock_error> clear_socket_interrupt(socket_id sn, uint8_t flags);
uint8_t get_socket_interrupt(socket_id sn);
std::expected<void, sock_error> set_socket_interrupt_mask(socket_id sn, uint8_t mask);
uint8_t get_socket_interrupt_mask(socket_id sn);
std::expected<void, sock_error> set_socket_prefer(socket_id sn, srcv6_prefer pref);
srcv6_prefer get_socket_prefer(socket_id sn);
void set_socket_ttl(socket_id sn, uint8_t ttl);
uint8_t get_socket_ttl(socket_id sn);
void set_socket_tos(socket_id sn, uint8_t tos);
uint8_t get_socket_tos(socket_id sn);
void set_socket_mss(socket_id sn, uint16_t mss);
uint16_t get_socket_mss(socket_id sn);
void set_socket_dest_ip(socket_id sn, const ip_address& addr);
ip_address get_socket_dest_ip(socket_id sn);
void set_socket_dest_port(socket_id sn, port_num port);
port_num get_socket_dest_port(socket_id sn);
std::expected<void, sock_error> send_keepalive(socket_id sn);
void set_socket_keepalive_auto(socket_id sn, uint8_t interval);
uint8_t get_socket_keepalive_auto(socket_id sn);
uint16_t get_socket_send_buf(socket_id sn);
uint16_t get_socket_recv_buf(socket_id sn);
uint8_t get_socket_status(socket_id sn);
uint8_t get_socket_ext_status(socket_id sn);
uint8_t get_socket_mode(socket_id sn);
uint16_t get_socket_remain_size(socket_id sn);
pack_info get_socket_pack_info(socket_id sn);
std::optional<uint16_t> peek_socket_msg(socket_id sn, std::span<const uint8_t> submsg);
} // namespace w6300
+59 -31
View File
@@ -7,7 +7,6 @@ import (
"github.com/theater/picomap/lib/halfsiphash"
"github.com/theater/picomap/lib/msgpack"
"io"
)
var HashKey = [8]byte{}
@@ -15,7 +14,8 @@ var HashKey = [8]byte{}
type transport interface {
Send(data []byte) error
SetReadTimeout(timeout time.Duration)
Reader() io.Reader
Recv() (data []byte, from string, err error)
Broadcast() bool
Close() error
}
@@ -44,51 +44,79 @@ func (c *Client) send(msg any) (uint32, error) {
return id, c.transport.Send(data)
}
func (c *Client) receive(expectedID uint32) (any, error) {
c.transport.SetReadTimeout(c.timeout)
dec := msgpack.NewDecoder(c.transport.Reader())
var env Envelope
if err := dec.Decode(&env); err != nil {
return nil, fmt.Errorf("decode envelope: %w", err)
}
if env.MessageID != expectedID {
return nil, fmt.Errorf("message id mismatch: got %d, want %d", env.MessageID, expectedID)
}
expected := halfsiphash.Sum32(env.Payload, HashKey)
if env.Checksum != expected {
return nil, fmt.Errorf("checksum mismatch: got %08x, want %08x", env.Checksum, expected)
}
var inner any
if err := msgpack.Unmarshal(env.Payload, &inner); err != nil {
return nil, fmt.Errorf("decode inner: %w", err)
}
if devErr, ok := inner.(*DeviceError); ok {
return nil, devErr
}
return inner, nil
type Response[T any] struct {
From string
Value *T
}
func roundTrip[T any](c *Client, req any) (*T, error) {
func roundTrip[T any](c *Client, req any) ([]Response[T], error) {
id, err := c.send(req)
if err != nil {
return nil, err
}
resp, err := c.receive(id)
c.transport.SetReadTimeout(c.timeout)
broadcast := c.transport.Broadcast()
var results []Response[T]
for {
data, from, err := c.transport.Recv()
if err != nil {
break
}
var env Envelope
if err := msgpack.Unmarshal(data, &env); err != nil {
continue
}
if env.MessageID != id {
continue
}
expected := halfsiphash.Sum32(env.Payload, HashKey)
if env.Checksum != expected {
continue
}
var inner any
if err := msgpack.Unmarshal(env.Payload, &inner); err != nil {
continue
}
if devErr, ok := inner.(*DeviceError); ok {
return nil, devErr
}
if typed, ok := inner.(*T); ok {
results = append(results, Response[T]{From: from, Value: typed})
if !broadcast {
break
}
}
}
return results, nil
}
func first[T any](results []Response[T], err error) (*T, error) {
if err != nil {
return nil, err
}
typed, ok := resp.(*T)
if !ok {
return nil, fmt.Errorf("unexpected response: %T", resp)
if len(results) == 0 {
return nil, fmt.Errorf("no response")
}
return typed, nil
return results[0].Value, nil
}
func (c *Client) PICOBOOT() error {
_, err := roundTrip[ResponsePICOBOOT](c, &RequestPICOBOOT{})
_, err := first(roundTrip[ResponsePICOBOOT](c, &RequestPICOBOOT{}))
return err
}
func (c *Client) Info() (*ResponseInfo, error) {
return first(roundTrip[ResponseInfo](c, &RequestInfo{}))
}
func (c *Client) InfoAll() ([]Response[ResponseInfo], error) {
return roundTrip[ResponseInfo](c, &RequestInfo{})
}
func (c *Client) Log() (*ResponseLog, error) {
return first(roundTrip[ResponseLog](c, &RequestLog{}))
}
func (c *Client) Test(name string) (*ResponseTest, error) {
return first(roundTrip[ResponseTest](c, &RequestTest{Name: name}))
}
+30 -8
View File
@@ -2,9 +2,11 @@ package client
import (
"fmt"
"io"
"slices"
"strings"
"time"
"github.com/theater/picomap/lib/msgpack"
"go.bug.st/serial"
"go.bug.st/serial/enumerator"
)
@@ -14,17 +16,30 @@ func ListSerial() ([]string, error) {
if err != nil {
return nil, fmt.Errorf("enumerating ports: %w", err)
}
var result []string
for _, p := range ports {
if p.IsUSB {
result = append(result, p.Name)
type entry struct {
name string
serial string
}
var entries []entry
for _, p := range ports {
if p.IsUSB && p.VID == "2E8A" && strings.HasPrefix(p.Name, "/dev/cu.") {
entries = append(entries, entry{p.Name, p.SerialNumber})
}
}
slices.SortFunc(entries, func(a, b entry) int {
return strings.Compare(a.serial, b.serial)
})
var result []string
for _, e := range entries {
result = append(result, e.name)
}
return result, nil
}
type serialTransport struct {
port serial.Port
portName string
dec *msgpack.Decoder
}
func NewSerial(portName string, timeout time.Duration) (*Client, error) {
@@ -32,7 +47,8 @@ func NewSerial(portName string, timeout time.Duration) (*Client, error) {
if err != nil {
return nil, fmt.Errorf("opening %s: %w", portName, err)
}
return &Client{transport: &serialTransport{port: port}, timeout: timeout}, nil
t := &serialTransport{port: port, portName: portName, dec: msgpack.NewDecoder(port)}
return &Client{transport: t, timeout: timeout}, nil
}
func (t *serialTransport) Send(data []byte) error {
@@ -44,10 +60,16 @@ func (t *serialTransport) SetReadTimeout(timeout time.Duration) {
t.port.SetReadTimeout(timeout)
}
func (t *serialTransport) Reader() io.Reader {
return t.port
func (t *serialTransport) Recv() ([]byte, string, error) {
var raw msgpack.RawMessage
if err := t.dec.Decode(&raw); err != nil {
return nil, "", err
}
return []byte(raw), t.portName, nil
}
func (t *serialTransport) Broadcast() bool { return false }
func (t *serialTransport) Close() error {
return t.port.Close()
}
+26
View File
@@ -9,6 +9,28 @@ type RequestInfo struct{}
type ResponseInfo struct {
BoardID [8]byte
MAC [6]byte
IP [4]byte
FirmwareName string
}
type RequestLog struct{}
type LogEntry struct {
TimestampUS uint32
Message string
}
type ResponseLog struct {
Entries []LogEntry
}
type RequestTest struct {
Name string
}
type ResponseTest struct {
Pass bool
Messages []string
}
type DeviceError struct {
@@ -33,4 +55,8 @@ func init() {
msgpack.RegisterExt(3, (*ResponsePICOBOOT)(nil))
msgpack.RegisterExt(4, (*RequestInfo)(nil))
msgpack.RegisterExt(5, (*ResponseInfo)(nil))
msgpack.RegisterExt(6, (*RequestLog)(nil))
msgpack.RegisterExt(7, (*ResponseLog)(nil))
msgpack.RegisterExt(127, (*RequestTest)(nil))
msgpack.RegisterExt(126, (*ResponseTest)(nil))
}
+130
View File
@@ -0,0 +1,130 @@
package client
import (
"fmt"
"net"
"syscall"
"time"
"golang.org/x/sys/unix"
)
const PicomapPort = 28781
type udpTransport struct {
conn *net.UDPConn
addr *net.UDPAddr
broadcast bool
}
func interfaceIPv4Net(name string) (net.IP, *net.IPNet, error) {
ifi, err := net.InterfaceByName(name)
if err != nil {
return nil, nil, fmt.Errorf("interface %s: %w", name, err)
}
addrs, err := ifi.Addrs()
if err != nil {
return nil, nil, fmt.Errorf("interface %s addrs: %w", name, err)
}
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok {
if ip4 := ipnet.IP.To4(); ip4 != nil {
return ip4, ipnet, nil
}
}
}
return nil, nil, fmt.Errorf("interface %s has no IPv4 address", name)
}
func broadcastAddr(ip net.IP, mask net.IPMask) net.IP {
bcast := make(net.IP, 4)
ip4 := ip.To4()
for i := range 4 {
bcast[i] = ip4[i] | ^mask[i]
}
return bcast
}
func InterfaceBroadcast(name string) (string, error) {
ip, ipnet, err := interfaceIPv4Net(name)
if err != nil {
return "", err
}
return broadcastAddr(ip, ipnet.Mask).String(), nil
}
func enableBroadcast(conn *net.UDPConn) error {
raw, err := conn.SyscallConn()
if err != nil {
return fmt.Errorf("syscall conn: %w", err)
}
var serr error
raw.Control(func(fd uintptr) {
serr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_BROADCAST, 1)
})
return serr
}
func isInterfaceBroadcast(iface string, ip net.IP) bool {
if iface == "" {
return false
}
localIP, ipnet, err := interfaceIPv4Net(iface)
if err != nil {
return false
}
return ip.Equal(broadcastAddr(localIP, ipnet.Mask)) || ip.Equal(net.IPv4bcast)
}
func NewUDP(addr string, iface string, timeout time.Duration) (*Client, error) {
raddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", addr, PicomapPort))
if err != nil {
return nil, fmt.Errorf("resolve %s: %w", addr, err)
}
var laddr *net.UDPAddr
if iface != "" {
ip, _, err := interfaceIPv4Net(iface)
if err != nil {
return nil, err
}
laddr = &net.UDPAddr{IP: ip, Port: 0}
}
conn, err := net.ListenUDP("udp4", laddr)
if err != nil {
return nil, fmt.Errorf("listen: %w", err)
}
if err := enableBroadcast(conn); err != nil {
conn.Close()
return nil, fmt.Errorf("SO_BROADCAST: %w", err)
}
bcast := isInterfaceBroadcast(iface, raddr.IP)
return &Client{transport: &udpTransport{conn: conn, addr: raddr, broadcast: bcast}, timeout: timeout}, nil
}
func (t *udpTransport) Send(data []byte) error {
_, err := t.conn.WriteToUDP(data, t.addr)
return err
}
func (t *udpTransport) SetReadTimeout(timeout time.Duration) {
t.conn.SetReadDeadline(time.Now().Add(timeout))
}
func (t *udpTransport) Recv() ([]byte, string, error) {
buf := make([]byte, 1500)
n, addr, err := t.conn.ReadFromUDP(buf)
if err != nil {
return nil, "", err
}
return buf[:n], addr.IP.String(), nil
}
func (t *udpTransport) Broadcast() bool { return t.broadcast }
func (t *udpTransport) Close() error {
return t.conn.Close()
}
+13 -5
View File
@@ -3,15 +3,23 @@ package picotool
import (
"fmt"
"os/exec"
"time"
)
func Load(uf2Path string, serial string) error {
cmd := exec.Command("picotool", "load", uf2Path, "--ser", serial)
out, err := cmd.CombinedOutput()
if err != nil {
func Load(uf2Path string, serial string, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
var out []byte
var err error
for {
cmd := exec.Command("picotool", "load", uf2Path, "-x", "--ser", serial)
out, err = cmd.CombinedOutput()
if err == nil {
return nil
}
if time.Now().After(deadline) {
return fmt.Errorf("picotool load: %w\n%s", err, out)
}
return nil
}
}
func Reboot(serial string) error {