Этот коммит содержится в:
Ayke van Laethem 2023-12-22 11:48:44 +01:00 коммит произвёл BCG
родитель ffe6dfd21b
коммит 8d2a07b927
5 изменённых файлов: 294 добавлений и 35 удалений

Просмотреть файл

@ -777,6 +777,8 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro @$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go

Просмотреть файл

@ -10,7 +10,7 @@ import (
var ( var (
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSchedulerOptions = []string{"none", "tasks", "asyncify"}
validSerialOptions = []string{"none", "uart", "usb"} validSerialOptions = []string{"none", "uart", "usb", "rtt"}
validPrintSizeOptions = []string{"none", "short", "full"} validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"}

Просмотреть файл

@ -532,7 +532,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return fmt.Errorf("unknown flash method: %s", flashMethod) return fmt.Errorf("unknown flash method: %s", flashMethod)
} }
if options.Monitor { if options.Monitor {
return Monitor(result.Executable, "", options) return Monitor(result.Executable, "", config)
} }
return nil return nil
} }
@ -1740,7 +1740,9 @@ func main() {
fmt.Printf("%s %4s %4s %s\n", s.Name, s.VID, s.PID, s.Target) fmt.Printf("%s %4s %4s %s\n", s.Name, s.VID, s.PID, s.Target)
} }
} else { } else {
err := Monitor("", *port, options) config, err := builder.NewConfig(options)
handleCompilerError(err)
err = Monitor("", *port, config)
handleCompilerError(err) handleCompilerError(err)
} }
case "targets": case "targets":

Просмотреть файл

@ -1,6 +1,7 @@
package main package main
import ( import (
"bufio"
"debug/dwarf" "debug/dwarf"
"debug/elf" "debug/elf"
"debug/macho" "debug/macho"
@ -9,6 +10,7 @@ import (
"fmt" "fmt"
"go/token" "go/token"
"io" "io"
"net"
"os" "os"
"os/signal" "os/signal"
"regexp" "regexp"
@ -17,7 +19,6 @@ import (
"time" "time"
"github.com/mattn/go-tty" "github.com/mattn/go-tty"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"go.bug.st/serial" "go.bug.st/serial"
@ -25,44 +26,151 @@ import (
) )
// Monitor connects to the given port and reads/writes the serial port. // Monitor connects to the given port and reads/writes the serial port.
func Monitor(executable, port string, options *compileopts.Options) error { func Monitor(executable, port string, config *compileopts.Config) error {
config, err := builder.NewConfig(options) const timeout = time.Second * 3
if err != nil { var exit func() // function to be called before exiting
return err var serialConn io.ReadWriter
}
wait := 300 if config.Options.Serial == "rtt" {
for i := 0; i <= wait; i++ { // Use the RTT interface, which is documented (in part) here:
port, err = getDefaultPort(port, config.Target.SerialPort) // https://wiki.segger.com/RTT
// Try to find the "machine.rttSerialInstance" symbol, which is the RTT
// control block.
file, err := elf.Open(executable)
if err != nil { if err != nil {
if i < wait { return fmt.Errorf("could not open ELF file to determine RTT control block: %w", err)
time.Sleep(10 * time.Millisecond) }
continue defer file.Close()
symbols, err := file.Symbols()
if err != nil {
return fmt.Errorf("could not read ELF symbol table to determine RTT control block: %w", err)
}
var address uint64
for _, symbol := range symbols {
if symbol.Name == "machine.rttSerialInstance" {
address = symbol.Value
break
} }
}
if address == 0 {
return fmt.Errorf("could not find RTT control block in ELF file")
}
// Start an openocd process in the background.
args, err := config.OpenOCDConfiguration()
if err != nil {
return err return err
} }
break args = append(args,
} "-c", fmt.Sprintf("rtt setup 0x%x 16 \"SEGGER RTT\"", address),
"-c", "init",
br := options.BaudRate "-c", "rtt server start 0 0")
if br <= 0 { cmd := executeCommand(config.Options, "openocd", args...)
br = 115200 stderr, err := cmd.StderrPipe()
}
wait = 300
var p serial.Port
for i := 0; i <= wait; i++ {
p, err = serial.Open(port, &serial.Mode{BaudRate: br})
if err != nil { if err != nil {
if i < wait {
time.Sleep(10 * time.Millisecond)
continue
}
return err return err
} }
break cmd.Stdout = os.Stdout
err = cmd.Start()
if err != nil {
return err
}
defer cmd.Process.Kill()
exit = func() {
// Make sure the openocd process is terminated at exit.
// This does not happen through the defer above when exiting through
// os.Exit.
cmd.Process.Kill()
}
// Read the stderr, which logs various important messages we need.
r := bufio.NewReader(stderr)
var telnet net.Conn
var timeoutAt time.Time
for {
// Read the next line from the openocd process.
lineBytes, err := r.ReadBytes('\n')
if err != nil {
return err
}
line := string(lineBytes)
if line == "Info : rtt: No control block found\n" {
// Message that is sent back when OpenOCD can't find the control
// block after a 'rtt start' message.
if time.Now().After(timeoutAt) {
return fmt.Errorf("RTT timeout (could not locate RTT control block at 0x%08x)", address)
}
time.Sleep(time.Millisecond * 100)
telnet.Write([]byte("rtt start\r\n"))
} else if strings.HasPrefix(line, "Info : Listening on port") {
// We need two different ports for controlling OpenOCD
// (typically port 4444) and the RTT channel 0 socket (arbitrary
// port).
var port int
var protocol string
fmt.Sscanf(line, "Info : Listening on port %d for %s connections\n", &port, &protocol)
if protocol == "telnet" && telnet == nil {
// Connect to the "telnet" command line interface.
telnet, err = net.Dial("tcp4", fmt.Sprintf("localhost:%d", port))
if err != nil {
return err
}
// Tell OpenOCD to start scanning for the RTT control block.
telnet.Write([]byte("rtt start\r\n"))
// Also make sure we will time out if the control block just
// can't be found.
timeoutAt = time.Now().Add(timeout)
} else if protocol == "rtt" {
// Connect to the RTT channel, for both stdin and stdout.
conn, err := net.Dial("tcp4", fmt.Sprintf("localhost:%d", port))
if err != nil {
return err
}
serialConn = conn
}
} else if strings.HasPrefix(line, "Info : rtt: Control block found at") {
// Connection established!
break
}
}
} else { // -serial=uart or -serial=usb
var err error
wait := 300
for i := 0; i <= wait; i++ {
port, err = getDefaultPort(port, config.Target.SerialPort)
if err != nil {
if i < wait {
time.Sleep(10 * time.Millisecond)
continue
}
return err
}
break
}
br := config.Options.BaudRate
if br <= 0 {
br = 115200
}
wait = 300
var p serial.Port
for i := 0; i <= wait; i++ {
p, err = serial.Open(port, &serial.Mode{BaudRate: br})
if err != nil {
if i < wait {
time.Sleep(10 * time.Millisecond)
continue
}
return err
}
serialConn = p
break
}
defer p.Close()
} }
defer p.Close()
tty, err := tty.Open() tty, err := tty.Open()
if err != nil { if err != nil {
@ -77,6 +185,9 @@ func Monitor(executable, port string, options *compileopts.Options) error {
go func() { go func() {
<-sig <-sig
tty.Close() tty.Close()
if exit != nil {
exit()
}
os.Exit(0) os.Exit(0)
}() }()
@ -88,7 +199,7 @@ func Monitor(executable, port string, options *compileopts.Options) error {
buf := make([]byte, 100*1024) buf := make([]byte, 100*1024)
var line []byte var line []byte
for { for {
n, err := p.Read(buf) n, err := serialConn.Read(buf)
if err != nil { if err != nil {
errCh <- fmt.Errorf("read error: %w", err) errCh <- fmt.Errorf("read error: %w", err)
return return
@ -124,7 +235,7 @@ func Monitor(executable, port string, options *compileopts.Options) error {
if r == 0 { if r == 0 {
continue continue
} }
p.Write([]byte(string(r))) serialConn.Write([]byte(string(r)))
} }
}() }()

144
src/machine/serial-rtt.go Обычный файл
Просмотреть файл

@ -0,0 +1,144 @@
//go:build baremetal && serial.rtt
// Implement Segger RTT support.
// This is mostly useful for targets that only have a debug connection
// available, and no serial output (or input). It is somewhat like semihosting,
// but not unusably slow.
// It was originally specified by Segger, but support is available in OpenOCD
// for at least the DAPLink debuggers so I assume it works on any SWD debugger.
package machine
import (
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
// This symbol name is known by the compiler, see monitor.go.
var rttSerialInstance rttSerial
var Serial = &rttSerialInstance
func InitSerial() {
Serial.Configure(UARTConfig{})
}
const (
// Some constants, see:
// https://github.com/SEGGERMicro/RTT/blob/master/RTT/SEGGER_RTT.h
rttMaxNumUpBuffers = 1
rttMaxNumDownBuffers = 1
rttBufferSizeUp = 1024
rttBufferSizeDown = 16
rttModeNoBlockSkip = 0
rttModeNoBlockTrim = 1
rttModeBlockIfFifoFull = 2
)
// The debugger knows about the layout of this struct, so it must not change.
// This is SEGGER_RTT_CB.
type rttControlBlock struct {
id [16]volatile.Register8
maxNumUpBuffers int32
maxNumDownBuffers int32
buffersUp [rttMaxNumUpBuffers]rttBuffer
buffersDown [rttMaxNumDownBuffers]rttBuffer
}
// Up or down buffer.
// This is SEGGER_RTT_BUFFER_UP and SEGGER_RTT_BUFFER_DOWN.
type rttBuffer struct {
name *byte
buffer *volatile.Register8
bufferSize uint32
writeOffset volatile.Register32
readOffset volatile.Register32
flags uint32
}
// Static buffers, for the default up and down buffer.
var (
rttBufferUpData [rttBufferSizeUp]volatile.Register8
rttBufferDownData [rttBufferSizeDown]volatile.Register8
)
type rttSerial struct {
rttControlBlock
}
func (s *rttSerial) Configure(config UARTConfig) error {
s.maxNumUpBuffers = rttMaxNumUpBuffers
s.maxNumDownBuffers = rttMaxNumDownBuffers
s.buffersUp[0].name = &[]byte("Terminal\x00")[0]
s.buffersUp[0].buffer = &rttBufferUpData[0]
s.buffersUp[0].bufferSize = rttBufferSizeUp
s.buffersUp[0].flags = rttModeNoBlockSkip
s.buffersDown[0].name = &[]byte("Terminal\x00")[0]
s.buffersDown[0].buffer = &rttBufferDownData[0]
s.buffersDown[0].bufferSize = rttBufferSizeDown
s.buffersDown[0].flags = rttModeNoBlockSkip
id := "SEGGER RTT"
for i := 0; i < len(id); i++ {
s.id[i].Set(id[i])
}
return nil
}
func (b *rttBuffer) writeByte(c byte) {
state := interrupt.Disable()
readOffset := b.readOffset.Get()
writeOffset := b.writeOffset.Get()
newWriteOffset := writeOffset + 1
if newWriteOffset == b.bufferSize {
newWriteOffset = 0
}
if newWriteOffset != readOffset {
unsafe.Slice(b.buffer, b.bufferSize)[writeOffset].Set(c)
b.writeOffset.Set(newWriteOffset)
}
interrupt.Restore(state)
}
func (b *rttBuffer) readByte() byte {
readOffset := b.readOffset.Get()
writeOffset := b.writeOffset.Get()
for readOffset == writeOffset {
readOffset = b.readOffset.Get()
}
c := unsafe.Slice(b.buffer, b.bufferSize)[readOffset].Get()
b.readOffset.Set(readOffset + 1)
return c
}
func (b *rttBuffer) buffered() int {
readOffset := b.readOffset.Get()
writeOffset := b.writeOffset.Get()
return int((writeOffset - readOffset) % rttBufferSizeDown)
}
func (s *rttSerial) WriteByte(b byte) error {
s.buffersUp[0].writeByte(b)
return nil
}
func (s *rttSerial) ReadByte() (byte, error) {
return s.buffersDown[0].readByte(), errNoByte
}
func (s *rttSerial) Buffered() int {
return s.buffersDown[0].buffered()
}
func (s *rttSerial) Write(data []byte) (n int, err error) {
for _, v := range data {
s.WriteByte(v)
}
return len(data), nil
}