Compare commits

..

119 Commits

Author SHA1 Message Date
Ayke van Laethem 5c2753e54e all: release 0.28.0
These are some major or breaking changes:

 - Reflect support got improved a lot.
 - Interrupts became more strict: heap allocations an blocking
   operations, which have always been undefined behavior, now result in
   a panic.
 - `//go:wasmimport` was added
 - various new flags were added to `tinygo test`
 - the source location for panics is printed in the `-monitor` output
2023-06-10 17:11:37 +02:00
Damian Gryski 0212f0c008 compiler: limit level of pointer-to-pointer-to-... types 2023-06-09 17:30:02 +02:00
Damian Gryski f5f4751088 compiler,transform: fix for pointer-to-pointer type switches from @aykevl 2023-06-09 17:30:02 +02:00
Damian Gryski 62fb386d57 compiler,reflect: add tagged pointers for **T etc 2023-06-09 17:30:02 +02:00
ivoszz a59f92daf8 Add smoke test for target m5stick-c 2023-06-08 09:27:41 +02:00
ivoszz b7508818ee Support for the M5STACK M5StickC ESP32-PICO device 2023-06-08 09:27:41 +02:00
Damian Gryski 284e1acd87 compiler: update testdata 2023-06-08 07:55:37 +02:00
Damian Gryski 37849c4897 compiler,reflect: use two bits of the meta byte for comparable/isBinary
Fixes #3683
2023-06-08 07:55:37 +02:00
deadprogram 213e73ad84 build: only make comment on sizediff job when run from the main repo, take 2
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-06-07 12:54:15 +02:00
Ayke van Laethem 4a240827cb main: add -internal-nodwarf flag
This can be useful during development to avoid generating debug
information in IR.
2023-06-06 11:46:16 +02:00
Ayke van Laethem 92d9f856ab main: rename some flags to make them explicitly internal
These flags shouldn't be used by users, but they are occasionally
helpful for developers.
2023-06-06 11:46:16 +02:00
Damian Gryski ee5890f36f tools: use geomean for sizediff 2023-06-06 09:30:35 +02:00
soypat 744574193b machine/rp2040: fix i2c crash when getting abort while waiting for stop condition 2023-06-04 20:17:33 +02:00
soypat bbe755fb69 fix bug in rp2040 SetPeriod implementation 2023-06-03 13:16:32 +02:00
Pierre Constantineau 635d322703 Add bluemicro840 board 2023-06-01 21:04:36 +02:00
deadprogram ee90bdebff build: only make comment on sizediff job when run from the main repo
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-31 21:24:50 +02:00
soypat bcdb3fc681 fix go:noinlines in volatile package 2023-05-29 22:16:35 +02:00
Ayke van Laethem b7b23ace3f samd21: fix issue with WS2812 driver
The regular port access is around 4 cycles, instead of the usual 2
cycles for a store instruction on Cortex-M0+. The IOBUS however is
faster, I didn't measure exactly but I guess it's 2 cycles as expected.

This fixes a bug in the WS2812 driver that only happens on samd21 chips:
https://github.com/tinygo-org/drivers/issues/540
2023-05-29 20:45:31 +02:00
deadprogram f366cd53b7 build: explicitly pass the github token to GH action
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-23 11:06:02 +02:00
Ayke van Laethem da81784ee9 attiny1616: implement Pin.Get()
I didn't add this method in the initial PR.

Also, I found that a few of my assumptions were incorrect. I've changed
the code that configures the pin to make input (floating and pullup)
actually work. These chips really are quite different from all the older
AVRs.
2023-05-21 10:44:02 +02:00
Ayke van Laethem 481f60c5ea interp: add support for reading a pointer tag
This is necessary to get https://github.com/tinygo-org/tinygo/pull/3691
working.
2023-05-20 23:57:20 +02:00
Ayke van Laethem cf39db36fe interp: fix subtle bug in pointer xor
If a pointer value was xor'ed with a value other than 0, it would not
have been run at runtime but instead would fall through to the generic
integer operations. This would likely result in a "cannot convert
pointer to integer" panic.

This commit fixes this subtle case.
2023-05-20 23:57:20 +02:00
Ayke van Laethem 54c07b7de8 interp: move some often-repeated code into a function 2023-05-20 23:57:20 +02:00
Ayke van Laethem 2fb866ca86 avr: add attiny1616 support
This is just support for the chip, no boards are currently supported.
However, you can use this target on a custom board.

Notes:

  - This required a new runtime and machine implementation, because the
    hardware is actually very different (and much nicer than older
    AVRs!).
  - I had to update gen-device-avr to support this chip. This also
    affects the generated output of other AVRs, but I checked all chips
    we support and there shouldn't be any backwards incompatible
    changes.
  - I did not implement peripherals like UART, I2C, SPI, etc because I
    don't need them. That is left to do in the future.

You can flash these chips with only a UART and a 1kOhm resistor, which
is really nice (no special hardware needed). Here is the program I've
used for this purpose: https://pypi.org/project/pymcuprog/
2023-05-20 21:18:02 +02:00
Ayke van Laethem 4d11d552db avr: update gen-device-avr tool to support newer AVRs
This refactors gen-device-avr to output two different formats: one for
all the existing AVR chips (that don't really have the concept of a
peripheral, just a bunch of registers), and one for all the new chips
like the ATtiny1616 (tinyAVR 1-series and 2-series) that have
peripherals like the Cortex-M chips with type structs and instances.

I checked the generated code for all the AVR chips we have support for
(atmega1280, atmega1284p, atmega2560, atmega328p, atmega32u4, attiny85)
and while the generated Go code did change, it looks safe to me.
2023-05-20 21:18:02 +02:00
Ayke van Laethem b43bd9e62a avr: fix interrupt names for newer attiny chips
This only affects chips that aren't supported by TinyGo yet, so this
should be a safe change. Importantly, it fixes interrupts on the
ATtiny1616.
2023-05-20 21:18:02 +02:00
Rajat Jindal 572c22f66a add Settings to debug.BuildInfo
Signed-off-by: Rajat Jindal <rajatjindal83@gmail.com>
2023-05-20 14:49:31 +02:00
deadprogram 1b66da4dff build: add issues write permission to sizediff job
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-20 14:02:14 +02:00
Ayke van Laethem b08ff17f6b compiler: disallow most types in //go:wasmimport
This is for compatibility with upstream Go.
See https://github.com/golang/go/issues/59149 for more context.
2023-05-20 11:24:20 +02:00
Ayke van Laethem 41e787d504 compiler: add tests for error messages
The test is currently empty, but will be used in the next commit.
2023-05-20 11:24:20 +02:00
Ayke van Laethem 6dba16f28e compiler: only calculate functionInfo once
This is a small change that's not really important in itself, but it
avoids duplicate errors in a future commit that adds error messages to
//go:wasmimport.
2023-05-20 11:24:20 +02:00
deadprogram b336a1561f build: update GH actions-comment-pull-request to v2.3.1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-17 17:54:47 +02:00
Ayke van Laethem 4c682680ca compileopts: don't filter build tags, use specific build tags instead
This basically reverts https://github.com/tinygo-org/tinygo/pull/3357
and replaces it with a different mechanism to get to the same goal.

I do not think filtering tags like this is a good idea: it's the wrong
part of the compiler to be concerned with such tags (that part sets
tags, but doesn't modify existing tags). Instead, I've written the
//go:build lines in such a way that it has the same effect: WASI
defaults to leveldb, everything else defaults to fnv, and it's possible
to override the default using build tags.
2023-05-17 11:21:15 +02:00
Ayke van Laethem af76c807e2 windows: re-enable parallelism
This reverts https://github.com/tinygo-org/tinygo/pull/3525, because
that change didn't seem to stop the CI failures we have been seeing.
Instead, I've added thread support in
https://github.com/tinygo-org/tinygo/pull/3130 which IIRC fixed most of
the CI crashes.

Re-enabling parallelism should improve the performance of TinyGo a bit
on Windows.
2023-05-16 21:47:24 +02:00
sago35 cdbd850f80 machine: add DefaultUART to xiao 2023-05-16 20:10:44 +02:00
Damian Gryski d256804af7 src/reflect: remove overflow checks from uvarint32 2023-05-16 19:02:08 +02:00
Damian Gryski e3c96803c3 runtime: fix structField.data comment 2023-05-16 19:02:08 +02:00
Damian Gryski f55bb8e030 builder: bump sizes_test 2023-05-16 19:02:08 +02:00
Damian Gryski 07fb3a0cad compiler,reflect: make field offsets varints
Fixes #3686
2023-05-16 19:02:08 +02:00
deadprogram 02913563df build: add write permission to sizediff GH actions job to always be able to add comments
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-16 18:41:45 +02:00
Ayke van Laethem 535e64adbc reflect: optimize Zero() a little bit
It could be expensive to call Size() three times, and it is unnecessary.
Instead, do it only once.

This results in a very small reduction of binary size if Zero() is used.
2023-05-14 18:23:23 +02:00
Ayke van Laethem af936f3261 ci: add comment with binary size difference
This makes reviewing PRs a lot easier because I don't have to run this
myself :)

This only uses the drivers repo so far, which is a good starting point
but doesn't include binary size changes for WebAssembly for example. A
future change could add some real-world programs to get a better idea of
the real-world impact.

To be clear: the intention is not to just look at the number at the
bottom. It is important to look at the actual size difference to see the
overall pattern (like, the difference may be due to a few outlier).
2023-05-14 15:38:57 +02:00
sago35 d1a45f2efc machine/usb/hid: fix hidreport (2) 2023-05-14 12:17:01 +02:00
deadprogram b56a263d0d machine/flash: remove FlashBuffer, modify flash example to use BlockDevice interface
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-14 10:41:03 +02:00
Ayke van Laethem 6435f62dcc builder: implement Nordic DFU file writer in Go
This avoids a dependency on nrfutil. I have verified that it creates
equivalent zip files to a wasp-os DFU zip file I downloaded here:
https://github.com/wasp-os/wasp-os/releases/
I have also tested that it produces valid DFU files that can be uploaded
using the dfu.py program here to my PineTime:
https://github.com/wasp-os/ota-dfu-python/tree/3d6fd30d33c2b20bc86ff6b9269fddf4a1d4c7c6

There are some minor differences in the generated file that should not
matter in practice (JSON whitespace, firmware file name, zip
compression).
2023-05-13 09:52:42 +02:00
Ayke van Laethem f4c8c37b7b nrf: add ADC oversampling support
This is a lot easier to support than on other chips, and results in
noticeably better output when set to a higher value.
2023-05-09 19:51:05 +02:00
Ayke van Laethem 868812717f nrf: add sample time to ADC
This is important for inputs with a high input resistance. In those
cases, the sample time needs to be longer.
2023-05-09 19:51:05 +02:00
Ayke van Laethem e82f595f37 nrf: add ability to set the reference voltage
The reference voltage can't be 3.3V, so we pick 3.0V as this was the
previous default.
2023-05-09 19:51:05 +02:00
Ayke van Laethem e5af121553 nrf: refactor ADC code a little bit
* Initialize the ADC in Configure() (instead of in Get()).
  * Do not set all channels to "not connected" - that's already the
    reset value.
  * Don't disable the ADC after use. It's not necessary to disable
    (current consumption appears to remain the same whether enabled or
    disabled).
2023-05-09 19:51:05 +02:00
Ayke van Laethem 20fdbc1f9d pinetime: update the target file
* Rename pinetime-devkit0 to pinetime because the production device is
    almost the same hardware (the only noticeable difference is a
    different accelerometer, which isn't part of the board file).
  * Remove the UART and set serial to none. The UART uses a lot of
    current by default, so it seems better to disable it.

This is a breaking change, but honestly I think I'm the only one who has
ever actually used TinyGo for the PineTime and I'm fine with this
change :)
2023-05-09 19:44:00 +02:00
sago35 14fed59827 machine/usb/hid: fix hidreport 2023-05-07 10:25:40 +02:00
deadprogram d331aca296 machine/rp2040: correct write block size for flash
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-07 00:49:10 +02:00
deadprogram 7e8a2e8934 machine/rp2040: correct param for number of bytes to be erased by flash
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-07 00:49:10 +02:00
deadprogram e7363966a5 machine/atsam*, nrf, rp2040, stm32: correct error flashBlockDevice pad() function
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-06 23:24:01 +02:00
Daz Wilkin 373ab34492 Add dummy package for runtime/metrics to that modules that depend upon it (e.g. [Prometheus Go client library](https://github.com/prometheus/client_golang/blob/main/prometheus/internal/go_runtime_metrics.go)) will compile.
Avoids:
```console
package runtime/metrics is not in GOROOT
```

Fixes #3705
2023-05-06 14:55:17 +02:00
Achille 602f35a5ca os: implement os.(*File).WriteAt (#3697)
os: implement os.(*File).WriteAt

Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
2023-05-05 09:40:15 +02:00
deadprogram 1d5c5ca2ef machine/usb/descriptor: further refactor HID report creation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-05 07:44:03 +02:00
deadprogram 90935703e6 machine/usb/descriptor: rename and export Append() to make it easier to create new descriptors in user code
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-05 07:44:03 +02:00
deadprogram d8ee520bdc machine/usb/descriptor: refactor HID report creation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-05-05 07:44:03 +02:00
Ayke van Laethem e4da35486b tools: add sizediff tool
This tool can be very useful to compare binary sizes as output by
`-size=short`.

This is a tool I wrote a while ago. It's not perfect (we should probably
use a geomean) but it works well enough to get a good idea on the binary
size impact of a change.
2023-05-04 21:46:53 +02:00
Edoardo Vacchi 4e41e9084a cgo: allow LDFLAGS: --export=...
Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
2023-05-04 20:45:02 +02:00
Achille Roussel ee3af40cab os: implement os.(*File).ReadDir for -target=wasi
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
2023-05-03 21:24:11 +02:00
Achille Roussel 666312f63f implement Sync on stdioFileHandle
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
2023-05-03 19:37:53 +02:00
Achille Roussel ccfe92a58c os: add os.(*File).Sync
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
2023-05-03 19:37:53 +02:00
Achille Roussel 4fa6a132e2 syscall: add fsync using libc
Signed-off-by: Achille Roussel <achille.roussel@gmail.com>
2023-05-03 19:37:53 +02:00
deadprogram 1a67795fd3 examples/usb-midi: remove serial communication from MIDI example
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-29 10:06:52 +02:00
deadprogram c70daa2497 machine/usb: move MIDI under usb/adc (Audio Device Class) package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-29 10:06:52 +02:00
sago35 d28b58e4dd machine/usb/hid/mouse: add support for mouse back and forward 2023-04-28 17:41:00 +02:00
deadprogram 25b03414dc machine/usb/hid/joystick: handle case where we cannot find the correct HID descriptor
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-28 15:15:54 +02:00
deadprogram 2ab7ee6a8a machine/usb: refactoring descriptors into subpackage for modularity
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-28 15:15:54 +02:00
Damian Gryski 6a2dd35fe6 builder: bump sizes test values 2023-04-27 11:15:41 +02:00
Damian Gryski 4c0fbbfc7f add struct size and field offsets to reflect data 2023-04-27 11:15:41 +02:00
deadprogram 36bd66a858 docs: update README for brevity and to add further info about webassembly
also, add links to guides about OS-specific development on website
      for macOS and Windows.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-27 09:08:37 +02:00
deadprogram 9d7dd3dd4b docs: update LICENSE year
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-27 09:08:37 +02:00
Ayke van Laethem 9bf5d6a4aa builder: add link to compatibility matrix
For context, see: https://github.com/tinygo-org/tinygo-site/pull/327

It only needs to be updated every half year, so it's not too bad, and it
could be very useful to some people.
2023-04-27 02:44:58 +02:00
Ayke van Laethem 839edec64c cortexm: fix stack size offset
The old code was broken and led to a HardFault in a rather convoluted
way:

 1. The CFA offset was incorrect, in fact it was not aligned (the stack
    is supposed to always be aligned to 4 bytes at least).
 2. This unaligned size was then used for stack size calculations.
 3. A stack that wasn't a multiple of 4 was allocated.
 4. The calleeSavedRegs struct (in `(internal/task.state).archInit`) was
    not correctly aligned.
 5. Writing to this struct resulted in a HardFault.
2023-04-27 00:11:53 +02:00
Ayke van Laethem 7e05c92420 cortexm: add "gdb" as a debugger
At least on Arch Linux ARM, there is no gdb-multiarch or something, just
"gdb" and it works for 32-bit ARM as well.
2023-04-26 23:07:52 +02:00
Ayke van Laethem 0b2aec1164 runtime: improve panic message when heap allocating in an interrupt
The old message may have been confusing. With the new message, it should
be more clear that we mean a _heap_ allocation.
2023-04-26 20:04:06 +02:00
Ayke van Laethem ae381e74a5 main: print source location when a panic happens in -monitor
The previous commit started printing the instruction address for runtime
panics. This commit starts using this address to print the source
location.

Here is an example where this feature is very useful. There is a heap
allocation in the Bluetooth package, but we don't know where exactly.
Printing the instruction address of the panic is already useful, but
what is even more useful is looking up this address in the DWARF debug
information that's part of the binary:

    $ tinygo flash -target=circuitplay-bluefruit -monitor ./examples/heartrate
    Connected to /dev/ttyACM0. Press Ctrl-C to exit.
    tick 00:00.810
    tick 00:01.587
    tick 00:02.387
    tick 00:03.244
    panic: runtime error at 0x00027c4d: alloc in interrupt
    [tinygo: panic at /home/ayke/src/tinygo/bluetooth/adapter_sd.go:74:4]

To be clear, this path isn't stored on the microcontroller. It's stored
as part of the build, and `-monitor` just looks up the path from the
panic message.

Possible enhancements:

  - Print such an address for regular panics as well. I'm not sure
    that's so useful, as it's usually a lot easier to look up panics
    just by their message.
  - Use runtimePanicAt (instead of runtimePanic) in other locations, if
    that proves to be beneficial.
  - Print the TinyGo-generated output in some other color, to
    distinguish it from the regular console output.
  - Print more details when panicking (registers, stack values), and
    print an actual backtrace.
2023-04-26 18:40:35 +02:00
Ayke van Laethem 3392827c3e runtime: print the address where a panic happened
This is not very useful in itself, but makes it possible to detect this
address in the output. See the next commit.

This adds around 50 bytes to each binary (except for AVR and wasm). This
is unfortunate, but I think this feature is quite useful still.
A future enhancement might be to create a build tag for extended panic
information that's not set by default.
2023-04-26 18:40:35 +02:00
Scott Feldman 59838338ba Add machine.CPUReset() (#3595)
machine: Add machine.CPUReset() for cortexm
2023-04-25 19:10:47 +02:00
Ron Evans 76303f9db4 Revert "all: better errors when multiple mcus share VID/PID"
This reverts commit af9f19615b.
2023-04-25 18:18:23 +02:00
Ron Evans 4bf8b618e5 Revert "all: honour port for -monitor flash flag"
This reverts commit 7aac1a1391.
2023-04-25 18:18:23 +02:00
deadprogram c89a684ad2 machine/gba: rename display and make pointer receivers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-25 16:43:29 +02:00
Kenneth Bell 7aac1a1391 all: honour port for -monitor flash flag 2023-04-25 11:21:16 +02:00
Kenneth Bell af9f19615b all: better errors when multiple mcus share VID/PID 2023-04-25 11:21:16 +02:00
deadprogram 79b63dd041 device/gba: additional IO mapping for sound, DMA, SIO, and sprites
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-24 18:11:29 +02:00
Damian Gryski 06e34caa5f builder: print the status of the job that just completed 2023-04-22 10:01:02 +02:00
cui fliter 4e9f9e2773 fix some comments
Signed-off-by: cui fliter <imcusg@gmail.com>
2023-04-18 22:51:49 +02:00
Ayke van Laethem 64957c5254 samd51: fix ADC multisampling
Multisampling/averaging (using the Samples configuration property) was
returning incorrect values. When I investigated this, I found that the
samd51 gives erratic values when using multisampling together with fewer
than 16 bits resolution.

I fixed this by forcing 16 bit resolution when multisampling, and
adjusting the output to account for multisampling.

Found while reading the battery value on a pybadge, which gave
non-sensible values with Samples set to a value larger than 1.
2023-04-18 19:00:11 +02:00
Kenneth Bell 1bba5f5d7b targets: make msd-volume-name an array 2023-04-17 18:56:32 +02:00
deadprogram 48ef68dd86 examples: replace fmt with encoding/hex in usb-midi example
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-17 08:54:06 +02:00
Damian Gryski 8b9bee4cad main: don't print ok for a successful compile-only 2023-04-16 03:38:33 +02:00
Elliott Sales de Andrade b4c9b579b8 Switch interp tests to opaque pointers 2023-04-16 03:34:46 +02:00
Damian Gryski 2b1dc4fee5 testing: add -test.shuffle to order randomize test and benchmark order 2023-04-12 20:43:41 +02:00
Damian Gryski 0244bed033 testdata: add test for else/defer bug 2023-04-11 18:54:05 -07:00
Damian Gryski 60b23a7035 compiler: update test outputs 2023-04-11 18:54:05 -07:00
Damian Gryski 4326c8f10e compiler: ensure all defers have been seen before creating rundefers
Fixes #3643
2023-04-11 18:54:05 -07:00
Damian Gryski 1672610749 testing: move runtime.GC() call to runN to match upstream 2023-04-11 12:16:25 +02:00
Damian Gryski e00a2395d9 testing: fix benchmark logging output 2023-04-11 12:16:25 +02:00
sago35 42175496eb machine/atsamd51: remove extra BK0RDY clear 2023-04-10 09:16:52 +02:00
Ayke van Laethem 3b4e543f4e rp2040: use DMA for send-only SPI transfers
This improves slightly. It also is some groundwork for better DMA
support in TinyGo in the future.

I'm not entirely sure why it improves performance (in theory the old
code should already saturate the SPI bus) but it does, so 🤷
2023-04-04 12:22:52 +02:00
Kenneth Bell ad3e9e1a77 i2c: implement target mode for rp2040 and nrf 2023-04-04 09:36:42 +02:00
Kenneth Bell e0385e48d0 nrf: new peripheral type for nrf528xx chips 2023-04-04 09:36:42 +02:00
Kenneth Bell feadb9c85c nrf: move nrf52 family code to correct file name 2023-04-04 09:36:42 +02:00
Kenneth Bell 4bf7308d26 machine: make gosched available to machine package 2023-04-04 09:36:42 +02:00
Ayke van Laethem 19e4db45db samd51: use correct SPI frequency
The SPI frequency was rounded up, not rounded down. This meant that if
you wanted to configure 15MHz for example, it would pick the next
available frequency (24MHz). That's unsafe, the safe option is to round
down and the SPI support for most other chips also rounds down for this
reason.

In addition, I've improved SPI clock selection so that it will pick the
best clock of the two, widening the available frequencies. See the
comments in the patch for details.
2023-04-03 19:40:20 +02:00
sago35 71b44e79b3 machine/usb/hid/joystick: allow joystick settings override 2023-04-03 00:50:30 +02:00
deadprogram 9e97566b5f machine/usb/hid/joystick: move joystick under HID as it belongs and also remove duplicate code
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-04-03 00:50:30 +02:00
Ayke van Laethem 7d83e76833 rp2040: use 4MHz as default frequency for SPI
This matches other SPI implementations. I think the original value of
115200 was from a confusion with UART.
2023-04-02 01:04:37 +02:00
Damian Gryski 9e7882b1b7 reflect: uncomment a another test the fails and doc some that don't 2023-04-01 22:46:46 +02:00
Damian Gryski 0c4f9d1f19 reflect; uncomment SetZero (but comment out the parts that fail) 2023-04-01 22:46:46 +02:00
Damian Gryski a85cb22193 reflect: uncomment TestAppend and fix a bug it found 2023-04-01 22:46:46 +02:00
Damian Gryski 60bb832c89 reflect: handle Convert'ing between identical underlying types
Needed for go-jose/v3
2023-04-01 22:46:46 +02:00
Ayke van Laethem 6eda52a289 rp2040: remove SPI deadline
Removing it improves SPI performance by about 20% for me (updating the
display of a Gopher Badge).
2023-04-01 11:18:51 +02:00
Damian Gryski 8badf79af9 testing: remove unused variable left over from count prototyping 2023-03-31 19:17:02 -07:00
Damian Gryski 66d3c4edb8 main: fix typos in flag usage messages 2023-03-31 19:17:02 -07:00
Damian Gryski 63aaa43072 testing: add test.skip
Fixes #3056
2023-03-31 19:17:02 -07:00
Damian Gryski ee81c31884 testing: import new version of match.go 2023-03-31 19:17:02 -07:00
211 changed files with 6805 additions and 2388 deletions
+96
View File
@@ -0,0 +1,96 @@
name: Binary size difference
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
sizediff:
runs-on: ubuntu-22.04
permissions:
pull-requests: write
steps:
# Prepare, install tools
- name: Add GOBIN to $PATH
run: |
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0 # fetch all history (no sparse checkout)
submodules: true
- name: Install apt dependencies
run: |
echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
llvm-15-dev \
clang-15 \
libclang-15-dev \
lld-15
- name: Restore LLVM source cache
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-15-sizediff-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache Go
uses: actions/cache@v3
with:
key: go-cache-linux-sizediff-v1-${{ hashFiles('go.mod') }}
path: |
~/.cache/go-build
~/go/pkg/mod
- run: make gen-device -j4
- name: Download drivers repo
run: git clone https://github.com/tinygo-org/drivers.git
- name: Save HEAD
run: git branch github-actions-saved-HEAD HEAD
# Compute sizes for the dev branch
- name: Checkout dev branch
run: git checkout --no-recurse-submodules `git merge-base HEAD origin/dev`
- name: Build tinygo binary for the dev branch
run: go install
- name: Determine binary sizes on the dev branch
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-dev.txt)
# Compute sizes for the PR branch
- name: Checkout PR branch
run: git checkout --no-recurse-submodules github-actions-saved-HEAD
- name: Build tinygo binary for the PR branch
run: go install
- name: Determine binary sizes on the PR branch
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt)
# Create comment
# TODO: add a summary, something like:
# - overall size difference (percent)
# - number of binaries that grew / shrank / remained the same
# - don't show the full diff when no binaries changed
- name: Calculate size diff
run: ./tools/sizediff drivers/sizes-dev.txt drivers/sizes-pr.txt | tee sizediff.txt
- name: Create comment
run: |
echo "Size difference with the dev branch:" > comment.txt
echo "<details><summary>Binary size difference</summary>" >> comment.txt
echo "<pre>" >> comment.txt
cat sizediff.txt >> comment.txt
echo "</pre></details>" >> comment.txt
- name: Comment contents
run: cat comment.txt
- name: Add comment
if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
uses: thollander/actions-comment-pull-request@v2.3.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
filePath: comment.txt
comment_tag: sizediff
+137
View File
@@ -1,3 +1,140 @@
0.28.0
---
* **general**
- fix parallelism in the compiler on Windows by building LLVM with thread support
- support qemu-user debugging
- make target JSON msd-volume-name an array
- print source location when a panic happens in -monitor
- `test`: don't print `ok` for a successful compile-only
* **compiler**
- `builder`: remove non-ThinLTO build mode
- `builder`: fail earlier if Go is not available
- `builder`: improve `-size=full` in a number of ways
- `builder`: implement Nordic DFU file writer in Go
- `cgo`: allow `LDFLAGS: --export=...`
- `compiler`: support recursive slice types
- `compiler`: zero struct padding during map operations
- `compiler`: add llvm.ident metadata
- `compiler`: remove `unsafe.Pointer(uintptr(v) + idx)` optimization (use `unsafe.Add` instead)
- `compiler`: add debug info to `//go:embed` data structures for better `-size` output
- `compiler`: add debug info to string constants
- `compiler`: fix a minor race condition
- `compiler`: emit correct alignment in debug info for global variables
- `compiler`: correctly generate reflect data for local named types
- `compiler`: add alloc attributes to `runtime.alloc`, reducing flash usage slightly
- `compiler`: for interface maps, use the original named type if available
- `compiler`: implement most math/bits functions as LLVM intrinsics
- `compiler`: ensure all defers have been seen before creating rundefers
* **standard library**
- `internal/task`: disallow blocking inside an interrupt
- `machine`: add `CPUReset`
- `machine/usb/hid`: add MediaKey support
- `machine/usb/hid/joystick`: move joystick under HID
- `machine/usb/hid/joystick`: allow joystick settings override
- `machine/usb/hid/joystick`: handle case where we cannot find the correct HID descriptor
- `machine/usb/hid/mouse`: add support for mouse back and forward
- `machine/usb`: add ability to override default VID, PID, manufacturer name, and product name
- `net`: added missing `TCPAddr` and `UDPAddr` implementations
- `os`: add IsTimeout function
- `os`: fix resource leak in `(*File).Close`
- `os`: add `(*File).Sync`
- `os`: implement `(*File).ReadDir` for wasi
- `os`: implement `(*File).WriteAt`
- `reflect`: make sure null bytes are supported in tags
- `reflect`: refactor this package to enable many new features
- `reflect`: add map type methods: `Elem` and `Key`
- `reflect`: add map methods: `MapIndex`, `MapRange`/`MapIter`, `SetMapIndex`, `MakeMap`, `MapKeys`
- `reflect`: add slice methods: `Append`, `MakeSlice`, `Slice`, `Slice3`, `Copy`, `Bytes`, `SetLen`
- `reflect`: add misc methods: `Zero`, `Addr`, `UnsafeAddr`, `OverflowFloat`, `OverflowInt`, `OverflowUint`, `SetBytes`, `Convert`, `CanInt`, `CanFloat`, `CanComplex`, `Comparable`
- `reflect`: add type methods: `String`, `PkgPath`, `FieldByName`, `FieldByIndex`, `NumMethod`
- `reflect`: add stubs for `Type.Method`, `CanConvert`, `ArrayOf`, `StructOf`, `MapOf`
- `reflect`: add stubs for channel select routines/types
- `reflect`: allow nil rawType to call Kind()
- `reflect`: ensure all ValueError panics have Kind fields
- `reflect`: add support for named types
- `reflect`: improve `Value.String()`
- `reflect`: set `Index` and `PkgPath` field in `Type.Field`
- `reflect`: `Type.AssignableTo`: you can assign anything to `interface{}`
- `reflect`: add type check to `Value.Field`
- `reflect`: let `TypeOf(nil)` return nil
- `reflect`: move `StructField.Anonymous` field to match upstream location
- `reflect`: add `UnsafePointer` for Func types
- `reflect`: `MapIter.Next` needs to allocate new keys/values every time
- `reflect`: fix `IsNil` for interfaces
- `reflect`: fix `Type.Name` to return an empty string for non-named types
- `reflect`: add `VisibleFields`
- `reflect`: properly handle embedded structs
- `reflect`: make sure `PointerTo` works for named types
- `reflect`: `Set`: convert non-interface to interface
- `reflect`: `Set`: fix direction of assignment check
- `reflect`: support channel directions
- `reflect`: print struct tags in Type.String()
- `reflect`: properly handle read-only values
- `runtime`: allow custom-gc SetFinalizer and clarify KeepAlive
- `runtime`: implement KeepAlive using inline assembly
- `runtime`: check for heap allocations inside interrupts
- `runtime`: properly turn pointer into empty interface when hashing
- `runtime`: improve map size hint usage
- `runtime`: zero map key/value on deletion to so GC doesn't see them
- `runtime`: print the address where a panic happened
- `runtime/debug`: stub `SetGCPercent`, `BuildInfo.Settings`
- `runtime/metrics`: add this package as a stub
- `syscall`: `Stat_t` timespec fields are Atimespec on darwin
- `syscall`: add `Timespec.Unix()` for wasi
- `syscall`: add fsync using libc
- `testing`: support -test.count
- `testing`: make test output unbuffered when verbose
- `testing`: add -test.skip
- `testing`: move runtime.GC() call to runN to match upstream
- `testing`: add -test.shuffle to order randomize test and benchmark order
* **targets**
- `arm64`: fix register save/restore to include vector registers
- `attiny1616`: add support for this chip
- `cortexm`: refactor EnableInterrupts and DisableInterrupts to avoid `arm.AsmFull`
- `cortexm`: enable functions in RAM for go & cgo
- `cortexm`: convert SystemStack from `AsmFull` to C inline assembly
- `cortexm`: fix crash due to wrong stack size offset
- `nrf`: samd21, stm32: add flash API
- `nrf`: fix memory issue in ADC read
- `nrf`: new peripheral type for nrf528xx chips
- `nrf`: implement target mode
- `nrf`: improve ADC and add oversampling, longer sample time, and reference voltage
- `rp2040`: change calling order for device enumeration fix to do first
- `rp2040`: rtc delayed interrupt
- `rp2040`: provide better errors for invalid pins on I2C and SPI
- `rp2040`: change uart to allow for a single pin
- `rp2040`: implement Flash interface
- `rp2040`: remove SPI `DataBits` property
- `rp2040`: unify all linker scripts using LDFLAGS
- `rp2040`: remove SPI deadline for improved performance
- `rp2040`: use 4MHz as default frequency for SPI
- `rp2040`: implement target mode
- `rp2040`: use DMA for send-only SPI transfers
- `samd21`: rearrange switch case for get pin cfg
- `samd21`: fix issue with WS2812 driver by making pin accesses faster
- `samd51`: enable CMCC cache for greatly improved performance
- `samd51`: remove extra BK0RDY clear
- `samd51`: implement Flash interface
- `samd51`: use correct SPI frequency
- `samd51`: remove extra BK0RDY clear
- `samd51`: fix ADC multisampling
- `wasi`: allow users to set the `runtime_memhash_tsip` or `runtime_memhash_fnv` build tags
- `wasi`: set `WASMTIME_BACKTRACE_DETAILS` when running in wasmtime.
- `wasm`: implement the `//go:wasmimport` directive
* **boards**
- `gameboy-advance`: switch to use register definitions in device/gba
- `gameboy-advance`: rename display and make pointer receivers
- `gopher-badge`: Added Gopher Badge support
- `lorae5`: add needed definition for UART2
- `lorae5`: correct mapping for I2C bus, add pin mapping to enable power
- `pinetime`: update the target file (rename from pinetime-devkit0)
- `qtpy`: fix bad pin assignment
- `wioterminal`: fix pin definition of BCM13
- `xiao`: Pins D4 & D5 are I2C1. Use pins D2 & D3 for I2C0.
- `xiao`: add DefaultUART
0.27.0
---
+2 -2
View File
@@ -1,7 +1,7 @@
Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2022 The Go Authors. All rights reserved.
Copyright (c) 2009-2023 The Go Authors. All rights reserved.
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
+9 -1
View File
@@ -482,6 +482,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/i2c-target
@$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org
ifneq ($(WASM), 0)
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
@@ -526,6 +528,8 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=bluemicro840 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
@@ -562,7 +566,7 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
$(TINYGO) build -size short -o test.hex -target=pinetime examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
@$(MD5SUM) test.hex
@@ -702,6 +706,8 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=attiny1616 examples/empty
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@@ -715,6 +721,8 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stick-c examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 examples/serial
@$(MD5SUM) test.bin
endif
+50 -101
View File
@@ -2,10 +2,12 @@
[![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools.
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
## Embedded
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
```go
@@ -35,115 +37,62 @@ The above program can be compiled and run without modification on an Arduino Uno
tinygo flash -target arduino examples/blinky1
```
## WebAssembly
TinyGo is very useful for compiling programs both for use in browsers (WASM) as well as for use on servers and other edge devices (WASI).
TinyGo programs can run in Fastly Compute@Edge (https://developer.fastly.com/learning/compute/go/), Fermyon Spin (https://developer.fermyon.com/spin/go-components), wazero (https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes.
Here is a small TinyGo program for use by a WASI host application:
```go
package main
//go:wasm-module yourmodulename
//export add
func add(x, y uint32) uint32 {
return x + y
}
// main is required for the `wasi` target, even if it isn't used.
func main() {}
```
This compiles the above TinyGo program for use on any WASI runtime:
```shell
tinygo build -o main.wasm -target=wasi main.go
```
## Installation
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
## Supported boards/targets
## Supported targets
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
### Embedded
The following 94 microcontroller boards are currently supported:
You can compile TinyGo programs for over 94 different microcontroller boards.
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M0 Express](https://www.adafruit.com/product/3403)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
* [Adafruit KB2040](https://www.adafruit.com/product/5302)
* [Adafruit MacroPad RP2040](https://www.adafruit.com/product/5100)
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
* [Adafruit QT Py RP2040](https://www.adafruit.com/product/4900)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Adafruit Trinkey QT2040](https://adafruit.com/product/5056)
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/)
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
* [blues wireless Swan](https://blues.io/products/swan/)
* [Digispark](http://digistump.com/products/1)
* [Dragino LoRaWAN GPS Tracker LGT-92](http://www.dragino.com/products/lora-lorawan-end-node/item/142-lgt-92.html)
* [ESP32 - Core board](https://www.espressif.com/en/products/socs/esp32)
* [ESP32 - mini32](https://www.espressif.com/en/products/socs/esp32)
* [ESP32-C3-12f](https://www.espressif.com/en/products/socs/esp32-c3)
* [ESP8266 - d1mini](https://www.espressif.com/en/products/socs/esp8266)
* [ESP8266 - NodeMCU](https://www.espressif.com/en/products/socs/esp8266)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [iLabs Challenger RP2040 LoRa](https://ilabs.se/product/challenger-rp2040-lora/)
* [M5Stack](https://docs.m5stack.com/en/core/basic)
* [M5Stack Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
* [M5Stamp C3](https://docs.m5stack.com/en/core/stamp_c3)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [MCH2022 badge](https://badge.team/docs/badges/mch2022/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
* [Nintendo Switch](https://www.nintendo.com/switch/)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [Pimoroni Badger2040](https://shop.pimoroni.com/products/badger-2040)
* [Pimoroni Tufty2040](https://shop.pimoroni.com/products/tufty-2040)
* [PineTime DevKit](https://www.pine64.org/pinetime/)
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [PJRC Teensy 4.1](https://www.pjrc.com/store/teensy41.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed XIAO BLE](https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html)
* [Seeed XIAO ESP32C3](https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html)
* [Seeed XIAO RP2040](https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html)
* [Seeed LoRa-E5 Development Kit](https://www.seeedstudio.com/LoRa-E5-Dev-Kit-p-4868.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [SiFIve HiFive1 Rev B](https://www.sifive.com/boards/hifive1-rev-b)
* [Sparkfun Thing Plus RP2040](https://www.sparkfun.com/products/17745)
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
* [ST Micro "Nucleo" WL55JC](https://www.st.com/en/evaluation-tools/nucleo-wl55jc.html)
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [ST Micro STM32F469 "Discovery"](https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-discovery-kits/32f469idiscovery.html)
* [The Things Industries Generic Node Sensor Edition](https://www.genericnode.com/docs/sensor-edition/)
* [Waveshare RP2040-Zero](https://www.waveshare.com/wiki/RP2040-Zero)
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
For more information, please see https://tinygo.org/docs/reference/microcontrollers/
### WebAssembly
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
TinyGo programs can be compiled for both WASM and WASI targets.
For more information, see https://tinygo.org/docs/guides/webassembly/
### Operating Systems
You can also compile programs for Linux, macOS, and Windows targets.
For more information:
- Linux https://tinygo.org/docs/guides/linux/
- macOS https://tinygo.org/docs/guides/macos/
- Windows https://tinygo.org/docs/guides/windows/
## Currently supported features:
+2 -7
View File
@@ -175,7 +175,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(),
NeedsStackObjects: config.NeedsStackObjects(),
Debug: true,
Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed
}
// Load the target machine, which is the LLVM object that contains all
@@ -909,13 +909,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
case "nrf-dfu":
// special format for nrfutil for Nordic chips
tmphexpath := filepath.Join(tmpdir, "main.hex")
err := objcopy(result.Executable, tmphexpath, "hex")
if err != nil {
return result, err
}
result.Binary = filepath.Join(tmpdir, "main"+outext)
err = makeDFUFirmwareImage(config.Options, tmphexpath, result.Binary)
err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary)
if err != nil {
return result, err
}
+2
View File
@@ -34,6 +34,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 18 || minor > 20 {
// Note: when this gets updated, also update the Go compatibility matrix:
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
return nil, fmt.Errorf("requires go version 1.18 through 1.20, got go%d.%d", major, minor)
}
+1 -1
View File
@@ -23,7 +23,7 @@ type espImageSegment struct {
data []byte
}
// makeESPFirmare converts an input ELF file to an image file for an ESP32 or
// makeESPFirmareImage converts an input ELF file to an image file for an ESP32 or
// ESP8266 chip. This is a special purpose image format just for the ESP chip
// family, and is parsed by the on-chip mask ROM bootloader.
//
+1 -1
View File
@@ -134,7 +134,7 @@ func runJobs(job *compileJob, sema chan struct{}) error {
numRunningJobs--
<-sema
if jobRunnerDebug {
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
fmt.Println("## finished:", completed.description, "(time "+completed.duration.String()+")")
}
if completed.err != nil {
// Wait for any current jobs to finish.
+109 -19
View File
@@ -1,27 +1,117 @@
package builder
import (
"fmt"
"io"
"os/exec"
"archive/zip"
"bytes"
"encoding/binary"
"encoding/json"
"os"
"github.com/sigurn/crc16"
"github.com/tinygo-org/tinygo/compileopts"
)
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
if options.PrintCommands != nil {
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
}
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
cmd.Stdout = io.Discard
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
return nil
// Structure of the manifest.json file.
type jsonManifest struct {
Manifest struct {
Application struct {
BinaryFile string `json:"bin_file"`
DataFile string `json:"dat_file"`
InitPacketData nrfInitPacket `json:"init_packet_data"`
} `json:"application"`
DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string
} `json:"manifest"`
}
// Structure of the init packet.
// Source:
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57
type nrfInitPacket struct {
ApplicationVersion uint32 `json:"application_version"`
DeviceRevision uint16 `json:"device_revision"`
DeviceType uint16 `json:"device_type"`
FirmwareCRC16 uint16 `json:"firmware_crc16"`
SoftDeviceRequired []uint16 `json:"softdevice_req"` // this is actually a variable length array
}
// Create the init packet (the contents of application.dat).
func (p nrfInitPacket) createInitPacket() []byte {
buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, p.DeviceType) // uint16_t device_type;
binary.Write(buf, binary.LittleEndian, p.DeviceRevision) // uint16_t device_rev;
binary.Write(buf, binary.LittleEndian, p.ApplicationVersion) // uint32_t app_version;
binary.Write(buf, binary.LittleEndian, uint16(len(p.SoftDeviceRequired))) // uint16_t softdevice_len;
binary.Write(buf, binary.LittleEndian, p.SoftDeviceRequired) // uint16_t softdevice[1];
binary.Write(buf, binary.LittleEndian, p.FirmwareCRC16)
return buf.Bytes()
}
// Make a Nordic DFU firmware image from an ELF file.
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
// Read ELF file as input and convert it to a binary image file.
_, data, err := extractROM(infile)
if err != nil {
return err
}
// Create the zip file in memory.
// It won't be very large anyway.
buf := &bytes.Buffer{}
w := zip.NewWriter(buf)
// Write the application binary to the zip file.
binw, err := w.Create("application.bin")
if err != nil {
return err
}
_, err = binw.Write(data)
if err != nil {
return err
}
// Create the init packet.
initPacket := nrfInitPacket{
ApplicationVersion: 0xffff_ffff, // appears to be unused by the Adafruit bootloader
DeviceRevision: 0xffff, // DFU_DEVICE_REVISION_EMPTY
DeviceType: 0x0052, // ADAFRUIT_DEVICE_TYPE
FirmwareCRC16: crc16.Checksum(data, crc16.MakeTable(crc16.CRC16_CCITT_FALSE)),
SoftDeviceRequired: []uint16{0xfffe}, // DFU_SOFTDEVICE_ANY
}
// Write the init packet to the zip file.
datw, err := w.Create("application.dat")
if err != nil {
return err
}
_, err = datw.Write(initPacket.createInitPacket())
if err != nil {
return err
}
// Create the JSON manifest.
manifest := &jsonManifest{}
manifest.Manifest.Application.BinaryFile = "application.bin"
manifest.Manifest.Application.DataFile = "application.dat"
manifest.Manifest.Application.InitPacketData = initPacket
manifest.Manifest.DFUVersion = 0.5
// Write the JSON manifest to the file.
jsonw, err := w.Create("manifest.json")
if err != nil {
return err
}
enc := json.NewEncoder(jsonw)
enc.SetIndent("", " ")
err = enc.Encode(manifest)
if err != nil {
return err
}
// Finish the zip file.
err = w.Close()
if err != nil {
return err
}
return os.WriteFile(outfile, buf.Bytes(), 0o666)
}
+3 -3
View File
@@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4556, 272, 0, 2252},
{"microbit", "examples/serial", 2680, 380, 8, 2256},
{"wioterminal", "examples/pininterrupt", 6109, 1471, 116, 6816},
{"hifive1b", "examples/echo", 4612, 280, 0, 2252},
{"microbit", "examples/serial", 2724, 388, 8, 2256},
{"wioterminal", "examples/pininterrupt", 6159, 1485, 116, 6816},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
+1
View File
@@ -142,6 +142,7 @@ var validLinkerFlags = []*regexp.Regexp{
re(`-L([^@\-].*)`),
re(`-O`),
re(`-O([^@\-].*)`),
re(`--export=([^@\-].*)`),
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?openmp(-simd)?`),
re(`-fsanitize=([^@\-].*)`),
+1
View File
@@ -108,6 +108,7 @@ var goodLinkerFlags = [][]string{
{"-Fbar"},
{"-lbar"},
{"-Lbar"},
{"--export=my_symbol"},
{"-fpic"},
{"-fno-pic"},
{"-fPIC"},
+3 -26
View File
@@ -75,8 +75,7 @@ func (c *Config) GOARM() string {
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
targetTags := filterTags(c.Target.BuildTags, c.Options.Tags)
tags := append(targetTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
@@ -545,32 +544,10 @@ type TestConfig struct {
Verbose bool
Short bool
RunRegexp string
SkipRegexp string
Count *int
BenchRegexp string
BenchTime string
BenchMem bool
}
// filterTags removes predefined build tags for a target if a conflicting option
// is provided by the user.
func filterTags(targetTags []string, userTags []string) []string {
var filtered []string
for _, t := range targetTags {
switch {
case strings.HasPrefix(t, "runtime_memhash_"):
overridden := false
for _, ut := range userTags {
if strings.HasPrefix(ut, "runtime_memhash_") {
overridden = true
break
}
}
if !overridden {
filtered = append(filtered, t)
}
default:
filtered = append(filtered, t)
}
}
return filtered
Shuffle string
}
-132
View File
@@ -1,132 +0,0 @@
package compileopts
import (
"fmt"
"strings"
"testing"
)
func TestBuildTags(t *testing.T) {
tests := []struct {
targetTags []string
userTags []string
result []string
}{
{
targetTags: []string{},
userTags: []string{},
result: []string{
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
},
},
{
targetTags: []string{"bear"},
userTags: []string{},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
},
},
{
targetTags: []string{},
userTags: []string{"cat"},
result: []string{
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear"},
userTags: []string{"cat"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat"},
result: []string{
"bear",
"runtime_memhash_leveldb",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat", "runtime_memhash_leveldb"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
"runtime_memhash_leveldb",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat", "runtime_memhash_sip"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
"runtime_memhash_sip",
},
},
}
for _, tc := range tests {
tt := tc
t.Run(fmt.Sprintf("%s+%s", strings.Join(tt.targetTags, ","), strings.Join(tt.userTags, ",")), func(t *testing.T) {
c := &Config{
Target: &TargetSpec{
BuildTags: tt.targetTags,
},
Options: &Options{
Tags: tt.userTags,
},
}
res := c.BuildTags()
if len(res) != len(tt.result) {
t.Errorf("expected %d tags, got %d", len(tt.result), len(res))
}
for i, tag := range tt.result {
if tag != res[i] {
t.Errorf("tag %d: expected %s, got %s", i, tt.result[i], tag)
}
}
})
}
}
+1
View File
@@ -35,6 +35,7 @@ type Options struct {
PrintIR bool
DumpSSA bool
VerifyIR bool
SkipDWARF bool
PrintCommands func(cmd string, args ...string) `json:"-"`
Semaphore chan struct{} `json:"-"` // -p flag controls cap
Debug bool
+1 -1
View File
@@ -50,7 +50,7 @@ type TargetSpec struct {
PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "vid:pid"
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashVolume []string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
UF2FamilyID string `json:"uf2-family-id"`
BinaryFormat string `json:"binary-format"`
+1 -1
View File
@@ -229,7 +229,7 @@ func extractSubfield(t types.Type, field int) types.Type {
}
}
// flattenAggregateTypeOffset returns the offsets from the start of an object of
// flattenAggregateTypeOffsets returns the offsets from the start of an object of
// type t if this object were flattened like in flattenAggregate. Used together
// with flattenAggregate to know the start indices of each value in the
// non-flattened object.
+28 -8
View File
@@ -82,6 +82,7 @@ type compilerContext struct {
uintptrType llvm.Type
program *ssa.Program
diagnostics []error
functionInfos map[*ssa.Function]functionInfo
astComments map[string]*ast.CommentGroup
embedGlobals map[string][]*loader.EmbedFile
pkg *types.Package
@@ -93,13 +94,14 @@ type compilerContext struct {
// importantly with a newly created LLVM context and module.
func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *Config, dumpSSA bool) *compilerContext {
c := &compilerContext{
Config: config,
DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
astComments: map[string]*ast.CommentGroup{},
Config: config,
DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
machine: machine,
targetData: machine.CreateTargetData(),
functionInfos: map[*ssa.Function]functionInfo{},
astComments: map[string]*ast.CommentGroup{},
}
c.ctx = llvm.NewContext()
@@ -167,6 +169,8 @@ type builder struct {
deferExprFuncs map[ssa.Value]int
selectRecvBuf map[*ssa.Select]llvm.Value
deferBuiltinFuncs map[ssa.Value]deferBuiltin
runDefersBlock []llvm.BasicBlock
afterDefersBlock []llvm.BasicBlock
}
func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder {
@@ -1349,6 +1353,15 @@ func (b *builder) createFunction() {
}
}
// The rundefers instruction needs to be created after all defer
// instructions have been created. Otherwise it won't handle all defer
// cases.
for i, bb := range b.runDefersBlock {
b.SetInsertPointAtEnd(bb)
b.createRunDefers()
b.CreateBr(b.afterDefersBlock[i])
}
if b.hasDeferFrame() {
// Create the landing pad block, where execution continues after a
// panic.
@@ -1503,7 +1516,14 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.CreateRet(retVal)
}
case *ssa.RunDefers:
b.createRunDefers()
// Note where we're going to put the rundefers block
run := b.insertBasicBlock("rundefers.block")
b.CreateBr(run)
b.runDefersBlock = append(b.runDefersBlock, run)
after := b.insertBasicBlock("rundefers.after")
b.SetInsertPointAtEnd(after)
b.afterDefersBlock = append(b.afterDefersBlock, after)
case *ssa.Send:
b.createChanSend(instr)
case *ssa.Store:
+85 -43
View File
@@ -70,52 +70,11 @@ func TestCompiler(t *testing.T) {
options := &compileopts.Options{
Target: targetString,
}
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatal("failed to load target:", err)
}
if tc.scheduler != "" {
options.Scheduler = tc.scheduler
}
config := &compileopts.Config{
Options: options,
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
Features: config.Features(),
ABI: config.ABI(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(),
NeedsStackObjects: config.NeedsStackObjects(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
defer machine.Dispose()
// Load entire program AST into memory.
lprogram, err := loader.Load(config, "./testdata/"+tc.file, config.ClangHeaders, types.Config{
Sizes: Sizes(machine),
})
if err != nil {
t.Fatal("failed to create target machine:", err)
}
err = lprogram.Parse()
if err != nil {
t.Fatalf("could not parse test case %s: %s", tc.file, err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
mod, errs := testCompilePackage(t, options, tc.file)
if errs != nil {
for _, err := range errs {
t.Error(err)
@@ -123,7 +82,7 @@ func TestCompiler(t *testing.T) {
return
}
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
err := llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil {
t.Error(err)
}
@@ -216,3 +175,86 @@ func filterIrrelevantIRLines(lines []string) []string {
}
return out
}
func TestCompilerErrors(t *testing.T) {
t.Parallel()
// Read expected errors from the test file.
var expectedErrors []string
errorsFile, err := os.ReadFile("testdata/errors.go")
if err != nil {
t.Error(err)
}
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
for _, line := range strings.Split(errorsFileString, "\n") {
if strings.HasPrefix(line, "// ERROR: ") {
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
}
}
// Compile the Go file with errors.
options := &compileopts.Options{
Target: "wasm",
}
_, errs := testCompilePackage(t, options, "errors.go")
// Check whether the actual errors match the expected errors.
expectedErrorsIdx := 0
for _, err := range errs {
err := err.(types.Error)
position := err.Fset.Position(err.Pos)
position.Filename = "errors.go" // don't use a full path
if expectedErrorsIdx >= len(expectedErrors) || expectedErrors[expectedErrorsIdx] != err.Msg {
t.Errorf("unexpected compiler error: %s: %s", position.String(), err.Msg)
continue
}
expectedErrorsIdx++
}
}
// Build a package given a number of compiler options and a file.
func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: options,
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
Features: config.Features(),
ABI: config.ABI(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(),
NeedsStackObjects: config.NeedsStackObjects(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
defer machine.Dispose()
// Load entire program AST into memory.
lprogram, err := loader.Load(config, "./testdata/"+file, config.ClangHeaders, types.Config{
Sizes: Sizes(machine),
})
if err != nil {
t.Fatal("failed to create target machine:", err)
}
err = lprogram.Parse()
if err != nil {
t.Fatalf("could not parse test case %s: %s", file, err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
}
+44 -7
View File
@@ -6,6 +6,7 @@ package compiler
// interface-lowering.go for more details.
import (
"encoding/binary"
"fmt"
"go/token"
"go/types"
@@ -126,6 +127,22 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
if _, ok := typ.Underlying().(*types.Interface); ok {
hasMethodSet = false
}
// Short-circuit all the global pointer logic here for pointers to pointers.
if typ, ok := typ.(*types.Pointer); ok {
if _, ok := typ.Elem().(*types.Pointer); ok {
// For a pointer to a pointer, we just increase the pointer by 1
ptr := c.getTypeCode(typ.Elem())
// if the type is already *****T or higher, we can't make it.
if typstr := typ.String(); strings.HasPrefix(typstr, "*****") {
c.addError(token.NoPos, fmt.Sprintf("too many levels of pointers for typecode: %s", typstr))
}
return llvm.ConstGEP(c.ctx.Int8Type(), ptr, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 1, false),
})
}
}
typeCodeName, isLocal := getTypeCodeName(typ)
globalName := "reflect/types.type:" + typeCodeName
var global llvm.Value
@@ -205,6 +222,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uint32]),
types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))),
)
@@ -236,6 +254,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
c.interfaceTypes.Set(typ, global)
}
metabyte := getTypeKind(typ)
// Precompute these so we don't have to calculate them at runtime.
if types.Comparable(typ) {
metabyte |= 1 << 6
}
if hashmapIsBinaryKey(typ) {
metabyte |= 1 << 7
}
switch typ := typ.(type) {
case *types.Basic:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
@@ -306,16 +334,21 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}
pkgPathPtr := c.pkgPathPtr(pkgpath)
llvmStructType := c.getLLVMType(typ)
size := c.targetData.TypeStoreSize(llvmStructType)
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
pkgPathPtr,
llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size
llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields
}
structFieldType := c.getLLVMRuntimeType("structField")
var fields []llvm.Value
for i := 0; i < typ.NumFields(); i++ {
field := typ.Field(i)
offset := c.targetData.ElementOffset(llvmStructType, i)
var flags uint8
if field.Anonymous() {
flags |= structFieldFlagAnonymous
@@ -329,7 +362,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
if field.Embedded() {
flags |= structFieldFlagIsEmbedded
}
data := string(flags) + field.Name() + "\x00"
var offsBytes [binary.MaxVarintLen32]byte
offLen := binary.PutUvarint(offsBytes[:], offset)
data := string(flags) + string(offsBytes[:offLen]) + field.Name() + "\x00"
if typ.Tag(i) != "" {
if len(typ.Tag(i)) > 0xff {
c.addError(field.Pos(), fmt.Sprintf("struct tag is %d bytes which is too long, max is 255", len(typ.Tag(i))))
@@ -370,6 +407,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}, typeFields...)
}
alignment := c.targetData.TypeAllocSize(c.i8ptrType)
if alignment < 4 {
alignment = 4
}
globalValue := c.ctx.ConstStruct(typeFields, false)
global.SetInitializer(globalValue)
if isLocal {
@@ -647,11 +687,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
} else {
assertedTypeGlobal := b.getTypeCode(expr.AssertedType)
if !assertedTypeGlobal.IsAConstantExpr().IsNil() {
assertedTypeGlobal = assertedTypeGlobal.Operand(0) // resolve the GEP operation
}
globalName := "reflect/types.typeid:" + strings.TrimPrefix(assertedTypeGlobal.Name(), "reflect/types.type:")
name, _ := getTypeCodeName(expr.AssertedType)
globalName := "reflect/types.typeid:" + name
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
@@ -724,7 +761,7 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string {
return strings.Join(methods, "; ")
}
// getInterfaceImplementsfunc returns a declared function that works as a type
// getInterfaceImplementsFunc returns a declared function that works as a type
// switch. The interface lowering pass will define this function.
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
s, _ := getTypeCodeName(assertedType.Underlying())
+64 -5
View File
@@ -4,6 +4,7 @@ package compiler
// pragmas, determines the link name, etc.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
@@ -239,18 +240,22 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// present in *ssa.Function, such as the link name and whether it should be
// exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
if info, ok := c.functionInfos[f]; ok {
return info
}
info := functionInfo{
// Pick the default linkName.
linkName: f.RelString(nil),
}
// Check for //go: pragmas, which may change the link name (among others).
info.parsePragmas(f)
c.parsePragmas(&info, f)
c.functionInfos[f] = info
return info
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline.
func (info *functionInfo) parsePragmas(f *ssa.Function) {
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
if f.Syntax() == nil {
return
}
@@ -290,10 +295,12 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
info.module = parts[1]
case "//go:wasmimport":
// Import a WebAssembly function, for example a WASI function.
// For details, see: https://github.com/golang/go/issues/38248
if len(parts) != 3 || len(f.Blocks) != 0 {
// Original proposal: https://github.com/golang/go/issues/38248
// Allow globally: https://github.com/golang/go/issues/59149
if len(parts) != 3 {
continue
}
c.checkWasmImport(f, comment.Text)
info.exported = true
info.module = parts[1]
info.importName = parts[2]
@@ -354,6 +361,58 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
}
}
// Check whether this function cannot be used in //go:wasmimport. It will add an
// error if this is the case.
//
// The list of allowed types is based on this proposal:
// https://github.com/golang/go/issues/59149
func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) {
if c.pkg.Path() == "runtime" {
// The runtime is a special case. Allow all kinds of parameters
// (importantly, including pointers).
return
}
if f.Blocks != nil {
// Defined functions cannot be exported.
c.addError(f.Pos(), fmt.Sprintf("can only use //go:wasmimport on declarations"))
return
}
if f.Signature.Results().Len() > 1 {
c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma))
} else if f.Signature.Results().Len() == 1 {
result := f.Signature.Results().At(0)
if !isValidWasmType(result.Type(), true) {
c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String()))
}
}
for _, param := range f.Params {
// Check whether the type is allowed.
// Only a very limited number of types can be mapped to WebAssembly.
if !isValidWasmType(param.Type(), false) {
c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String()))
}
}
}
// Check whether the type maps directly to a WebAssembly type, according to:
// https://github.com/golang/go/issues/59149
func isValidWasmType(typ types.Type, isReturn bool) bool {
switch typ := typ.Underlying().(type) {
case *types.Basic:
switch typ.Kind() {
case types.Int32, types.Uint32, types.Int64, types.Uint64:
return true
case types.Float32, types.Float64:
return true
case types.UnsafePointer:
if !isReturn {
return true
}
}
}
return false
}
// getParams returns the function parameters, including the receiver at the
// start. This is an alternative to the Params member of *ssa.Function, which is
// not yet populated when the package has not yet been built.
@@ -417,7 +476,7 @@ func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
}
}
// addStandardAttribute adds all attributes added to defined functions.
// addStandardAttributes adds all attributes added to defined functions.
func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
c.addStandardDeclaredAttributes(llvmFn)
c.addStandardDefinedAttributes(llvmFn)
+20 -8
View File
@@ -37,9 +37,16 @@ entry:
1: ; preds = %entry
call void @main.external(ptr undef) #4
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
rundefers.block: ; preds = %1
br label %rundefers.loophead
rundefers.loophead: ; preds = %3, %1
rundefers.loophead: ; preds = %3, %rundefers.block
%2 = load ptr, ptr %deferPtr, align 4
%stackIsNil = icmp eq ptr %2, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
@@ -66,8 +73,7 @@ rundefers.default: ; preds = %rundefers.loop
unreachable
rundefers.end: ; preds = %rundefers.loophead
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
br label %rundefers.after
recover: ; preds = %rundefers.end3
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
@@ -111,6 +117,8 @@ declare ptr @llvm.stacksave() #3
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #2
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2
; Function Attrs: nounwind
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 {
entry:
@@ -118,8 +126,6 @@ entry:
ret void
}
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2
declare void @runtime.printint32(i32, ptr) #2
; Function Attrs: nounwind
@@ -146,9 +152,16 @@ entry:
1: ; preds = %entry
call void @main.external(ptr undef) #4
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
rundefers.block: ; preds = %1
br label %rundefers.loophead
rundefers.loophead: ; preds = %4, %3, %1
rundefers.loophead: ; preds = %4, %3, %rundefers.block
%2 = load ptr, ptr %deferPtr, align 4
%stackIsNil = icmp eq ptr %2, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
@@ -185,8 +198,7 @@ rundefers.default: ; preds = %rundefers.loop
unreachable
rundefers.end: ; preds = %rundefers.loophead
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
br label %rundefers.after
recover: ; preds = %rundefers.end7
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
+43
View File
@@ -0,0 +1,43 @@
package main
import "unsafe"
//go:wasmimport modulename empty
func empty()
// ERROR: can only use //go:wasmimport on declarations
//
//go:wasmimport modulename implementation
func implementation() {
}
type Uint uint32
//go:wasmimport modulename validparam
func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint)
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type string
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type *int32
//
//go:wasmimport modulename invalidparam
func invalidparam(a int, b string, c []byte, d *int32)
//go:wasmimport modulename validreturn
func validreturn() int32
// ERROR: //go:wasmimport modulename manyreturns: too many return values
//
//go:wasmimport modulename manyreturns
func manyreturns() (int32, int32)
// ERROR: //go:wasmimport modulename invalidreturn: unsupported result type int
//
//go:wasmimport modulename invalidreturn
func invalidreturn() int
// ERROR: //go:wasmimport modulename invalidUnsafePointerReturn: unsupported result type unsafe.Pointer
//
//go:wasmimport modulename invalidUnsafePointerReturn
func invalidUnsafePointerReturn() unsafe.Pointer
+2 -2
View File
@@ -21,8 +21,8 @@ target triple = "wasm32-unknown-wasi"
@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 8
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " }
@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" }
@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 16, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4
@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
+8 -8
View File
@@ -6,15 +6,15 @@ target triple = "wasm32-unknown-wasi"
%runtime._interface = type { ptr, ptr }
%runtime._string = type { ptr, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 52, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4
@"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4
@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4
@"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4
@"reflect/types.typeid:basic:int" = external constant i8
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
-4
View File
@@ -62,10 +62,6 @@ func exportedFunctionInSection() {
//go:wasmimport modulename import1
func declaredImport()
//go:wasmimport modulename import2
func definedImport() {
}
// This function should not: it's only a declaration and not a definition.
//
//go:section .special_function_section
-6
View File
@@ -62,12 +62,6 @@ entry:
declare void @main.declaredImport() #7
; Function Attrs: nounwind
define hidden void @main.definedImport(ptr %context) unnamed_addr #2 {
entry:
ret void
}
declare void @main.undefinedFunctionNotInSection(ptr) #1
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
+1
View File
@@ -13,6 +13,7 @@ require (
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
github.com/mattn/go-colorable v0.1.8
github.com/mattn/go-tty v0.0.4
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
go.bug.st/serial v1.3.5
golang.org/x/sys v0.4.0
golang.org/x/tools v0.5.1-0.20230114154351-e035d0c426c8
+2
View File
@@ -43,6 +43,8 @@ github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3px
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE=
go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304=
+20 -57
View File
@@ -346,16 +346,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
dstObj.buffer = dstBuf
mem.put(dst.index(), dstObj)
}
switch inst.llvmInst.Type().IntTypeWidth() {
case 16:
locals[inst.localIndex] = literalValue{uint16(n)}
case 32:
locals[inst.localIndex] = literalValue{uint32(n)}
case 64:
locals[inst.localIndex] = literalValue{uint64(n)}
default:
panic("unknown integer type width")
}
locals[inst.localIndex] = makeLiteralInt(n, inst.llvmInst.Type().IntTypeWidth())
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"):
// Copy a block of memory from one pointer to another.
dst, err := operands[1].asPointer(r)
@@ -647,16 +638,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
}
// GEP on fixed pointer value (for example, memory-mapped I/O).
ptrValue := operands[0].Uint() + offset
switch operands[0].len(r) {
case 8:
locals[inst.localIndex] = literalValue{uint64(ptrValue)}
case 4:
locals[inst.localIndex] = literalValue{uint32(ptrValue)}
case 2:
locals[inst.localIndex] = literalValue{uint16(ptrValue)}
default:
panic("pointer operand is not of a known pointer size")
}
locals[inst.localIndex] = makeLiteralInt(ptrValue, int(operands[0].len(r)*8))
continue
}
ptr = ptr.addOffset(int64(offset))
@@ -754,30 +736,33 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
if err == nil {
// The lhs is a pointer. This sometimes happens for particular
// pointer tricks.
switch inst.opcode {
case llvm.Add:
if inst.opcode == llvm.Add {
// This likely means this is part of a
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
lhsPtr = lhsPtr.addOffset(int64(rhs.Uint()))
locals[inst.localIndex] = lhsPtr
continue
case llvm.Xor:
if rhs.Uint() == 0 {
// Special workaround for strings.noescape, see
// src/strings/builder.go in the Go source tree. This is
// the identity operator, so we can return the input.
locals[inst.localIndex] = lhs
continue
}
default:
} else if inst.opcode == llvm.Xor && rhs.Uint() == 0 {
// Special workaround for strings.noescape, see
// src/strings/builder.go in the Go source tree. This is
// the identity operator, so we can return the input.
locals[inst.localIndex] = lhs
} else if inst.opcode == llvm.And && rhs.Uint() < 8 {
// This is probably part of a pattern to get the lower bits
// of a pointer for pointer tagging, like this:
// uintptr(unsafe.Pointer(t)) & 0b11
// We can actually support this easily by ANDing with the
// pointer offset.
result := uint64(lhsPtr.offset()) & rhs.Uint()
locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8))
} else {
// Catch-all for weird operations that should just be done
// at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
continue
}
var result uint64
switch inst.opcode {
@@ -810,18 +795,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
default:
panic("unreachable")
}
switch lhs.len(r) {
case 8:
locals[inst.localIndex] = literalValue{result}
case 4:
locals[inst.localIndex] = literalValue{uint32(result)}
case 2:
locals[inst.localIndex] = literalValue{uint16(result)}
case 1:
locals[inst.localIndex] = literalValue{uint8(result)}
default:
panic("unknown integer size")
}
locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8))
if r.debug {
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", lhs, rhs, "->", result)
}
@@ -843,18 +817,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
if r.debug {
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
}
switch bitwidth {
case 64:
locals[inst.localIndex] = literalValue{value}
case 32:
locals[inst.localIndex] = literalValue{uint32(value)}
case 16:
locals[inst.localIndex] = literalValue{uint16(value)}
case 8:
locals[inst.localIndex] = literalValue{uint8(value)}
default:
panic("unknown integer size in sext/zext/trunc")
}
locals[inst.localIndex] = makeLiteralInt(value, int(bitwidth))
case llvm.SIToFP, llvm.UIToFP:
var value float64
switch inst.opcode {
+16
View File
@@ -373,6 +373,22 @@ type literalValue struct {
value interface{}
}
// Make a literalValue given the number of bits.
func makeLiteralInt(value uint64, bits int) literalValue {
switch bits {
case 64:
return literalValue{value}
case 32:
return literalValue{uint32(value)}
case 16:
return literalValue{uint16(value)}
case 8:
return literalValue{uint8(value)}
default:
panic("unknown integer size")
}
}
func (v literalValue) len(r *runner) uint32 {
switch v.value.(type) {
case uint64:
+28 -28
View File
@@ -2,17 +2,17 @@ target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " }
@pointerFree12 = global i8* null
@pointerFree7 = global i8* null
@pointerFree3 = global i8* null
@pointerFree0 = global i8* null
@layout1 = global i8* null
@layout2 = global i8* null
@layout3 = global i8* null
@layout4 = global i8* null
@bigobj1 = global i8* null
@pointerFree12 = global ptr null
@pointerFree7 = global ptr null
@pointerFree3 = global ptr null
@pointerFree0 = global ptr null
@layout1 = global ptr null
@layout2 = global ptr null
@layout3 = global ptr null
@layout4 = global ptr null
@bigobj1 = global ptr null
declare i8* @runtime.alloc(i32, i8*) unnamed_addr
declare ptr @runtime.alloc(i32, ptr) unnamed_addr
define void @runtime.initAll() unnamed_addr {
call void @main.init()
@@ -21,33 +21,33 @@ define void @runtime.initAll() unnamed_addr {
define internal void @main.init() unnamed_addr {
; Object that's word-aligned.
%pointerFree12 = call i8* @runtime.alloc(i32 12, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree12, i8** @pointerFree12
%pointerFree12 = call ptr @runtime.alloc(i32 12, ptr inttoptr (i32 3 to ptr))
store ptr %pointerFree12, ptr @pointerFree12
; Object larger than a word but not word-aligned.
%pointerFree7 = call i8* @runtime.alloc(i32 7, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree7, i8** @pointerFree7
%pointerFree7 = call ptr @runtime.alloc(i32 7, ptr inttoptr (i32 3 to ptr))
store ptr %pointerFree7, ptr @pointerFree7
; Object smaller than a word (and of course not word-aligned).
%pointerFree3 = call i8* @runtime.alloc(i32 3, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree3, i8** @pointerFree3
%pointerFree3 = call ptr @runtime.alloc(i32 3, ptr inttoptr (i32 3 to ptr))
store ptr %pointerFree3, ptr @pointerFree3
; Zero-sized object.
%pointerFree0 = call i8* @runtime.alloc(i32 0, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree0, i8** @pointerFree0
%pointerFree0 = call ptr @runtime.alloc(i32 0, ptr inttoptr (i32 3 to ptr))
store ptr %pointerFree0, ptr @pointerFree0
; Object made out of 3 pointers.
%layout1 = call i8* @runtime.alloc(i32 12, i8* inttoptr (i32 67 to i8*))
store i8* %layout1, i8** @layout1
%layout1 = call ptr @runtime.alloc(i32 12, ptr inttoptr (i32 67 to ptr))
store ptr %layout1, ptr @layout1
; Array (or slice) of 5 slices.
%layout2 = call i8* @runtime.alloc(i32 60, i8* inttoptr (i32 71 to i8*))
store i8* %layout2, i8** @layout2
%layout2 = call ptr @runtime.alloc(i32 60, ptr inttoptr (i32 71 to ptr))
store ptr %layout2, ptr @layout2
; Oddly shaped object, using all bits in the layout integer.
%layout3 = call i8* @runtime.alloc(i32 104, i8* inttoptr (i32 2467830261 to i8*))
store i8* %layout3, i8** @layout3
%layout3 = call ptr @runtime.alloc(i32 104, ptr inttoptr (i32 2467830261 to ptr))
store ptr %layout3, ptr @layout3
; ...repeated.
%layout4 = call i8* @runtime.alloc(i32 312, i8* inttoptr (i32 2467830261 to i8*))
store i8* %layout4, i8** @layout4
%layout4 = call ptr @runtime.alloc(i32 312, ptr inttoptr (i32 2467830261 to ptr))
store ptr %layout4, ptr @layout4
; Large object that needs to be stored in a separate global.
%bigobj1 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-2000000000000001" to i8*))
store i8* %bigobj1, i8** @bigobj1
%bigobj1 = call ptr @runtime.alloc(i32 248, ptr @"runtime/gc.layout:62-2000000000000001")
store ptr %bigobj1, ptr @bigobj1
ret void
}
+14 -14
View File
@@ -1,24 +1,24 @@
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
@pointerFree12 = local_unnamed_addr global i8* getelementptr inbounds ([12 x i8], [12 x i8]* @"main$alloc", i32 0, i32 0)
@pointerFree7 = local_unnamed_addr global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @"main$alloc.1", i32 0, i32 0)
@pointerFree3 = local_unnamed_addr global i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main$alloc.2", i32 0, i32 0)
@pointerFree0 = local_unnamed_addr global i8* getelementptr inbounds ([0 x i8], [0 x i8]* @"main$alloc.3", i32 0, i32 0)
@layout1 = local_unnamed_addr global i8* bitcast ([3 x i8*]* @"main$alloc.4" to i8*)
@layout2 = local_unnamed_addr global i8* bitcast ([5 x { i8*, i32, i32 }]* @"main$alloc.5" to i8*)
@layout3 = local_unnamed_addr global i8* bitcast ({ i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }* @"main$alloc.6" to i8*)
@layout4 = local_unnamed_addr global i8* bitcast ([3 x { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }]* @"main$alloc.7" to i8*)
@bigobj1 = local_unnamed_addr global i8* bitcast ({ i8*, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i8* }* @"main$alloc.8" to i8*)
@pointerFree12 = local_unnamed_addr global ptr @"main$alloc"
@pointerFree7 = local_unnamed_addr global ptr @"main$alloc.1"
@pointerFree3 = local_unnamed_addr global ptr @"main$alloc.2"
@pointerFree0 = local_unnamed_addr global ptr @"main$alloc.3"
@layout1 = local_unnamed_addr global ptr @"main$alloc.4"
@layout2 = local_unnamed_addr global ptr @"main$alloc.5"
@layout3 = local_unnamed_addr global ptr @"main$alloc.6"
@layout4 = local_unnamed_addr global ptr @"main$alloc.7"
@bigobj1 = local_unnamed_addr global ptr @"main$alloc.8"
@"main$alloc" = internal global [12 x i8] zeroinitializer, align 4
@"main$alloc.1" = internal global [7 x i8] zeroinitializer, align 4
@"main$alloc.2" = internal global [3 x i8] zeroinitializer, align 4
@"main$alloc.3" = internal global [0 x i8] zeroinitializer, align 4
@"main$alloc.4" = internal global [3 x i8*] zeroinitializer, align 4
@"main$alloc.5" = internal global [5 x { i8*, i32, i32 }] zeroinitializer, align 4
@"main$alloc.6" = internal global { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* } zeroinitializer, align 4
@"main$alloc.7" = internal global [3 x { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }] zeroinitializer, align 4
@"main$alloc.8" = internal global { i8*, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i8* } zeroinitializer, align 4
@"main$alloc.4" = internal global [3 x ptr] zeroinitializer, align 4
@"main$alloc.5" = internal global [5 x { ptr, i32, i32 }] zeroinitializer, align 4
@"main$alloc.6" = internal global { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr } zeroinitializer, align 4
@"main$alloc.7" = internal global [3 x { ptr, ptr, ptr, i32, i32, ptr, ptr, i32, i32, i32, i32, i32, i32, ptr, ptr, i32, i32, i32, ptr, ptr, i32, i32, ptr, i32, i32, ptr }] zeroinitializer, align 4
@"main$alloc.8" = internal global { ptr, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, ptr } zeroinitializer, align 4
define void @runtime.initAll() unnamed_addr {
ret void
+21 -21
View File
@@ -5,7 +5,7 @@ target triple = "x86_64--linux"
@main.nonConst1 = global [4 x i64] zeroinitializer
@main.nonConst2 = global i64 0
@main.someArray = global [8 x {i16, i32}] zeroinitializer
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exportedValue = global [1 x ptr] [ptr @main.exposedValue1]
@main.exportedConst = constant i64 42
@main.exposedValue1 = global i16 0
@main.exposedValue2 = global i16 0
@@ -24,7 +24,7 @@ entry:
define void @main() unnamed_addr {
entry:
%0 = load i64, i64* @main.v1
%0 = load i64, ptr @main.v1
call void @runtime.printint64(i64 %0)
call void @runtime.printnl()
ret void
@@ -37,43 +37,43 @@ entry:
define internal void @main.init() unnamed_addr {
entry:
store i64 3, i64* @main.v1
store i64 3, ptr @main.v1
call void @"main.init#1"()
; test the following pattern:
; func someValue() int // extern function
; var nonConst1 = [4]int{someValue(), 0, 0, 0}
%value1 = call i64 @someValue()
%gep1 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
store i64 %value1, i64* %gep1
%gep1 = getelementptr [4 x i64], ptr @main.nonConst1, i32 0, i32 0
store i64 %value1, ptr %gep1
; Test that the global really is marked dirty:
; var nonConst2 = nonConst1[0]
%gep2 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
%value2 = load i64, i64* %gep2
store i64 %value2, i64* @main.nonConst2
%gep2 = getelementptr [4 x i64], ptr @main.nonConst1, i32 0, i32 0
%value2 = load i64, ptr %gep2
store i64 %value2, ptr @main.nonConst2
; Test that the following GEP works:
; var someArray
; modifyExternal(&someArray[3].field1)
%gep3 = getelementptr [8 x {i16, i32}], [8 x {i16, i32}]* @main.someArray, i32 0, i32 3, i32 1
call void @modifyExternal(i32* %gep3)
%gep3 = getelementptr [8 x {i16, i32}], ptr @main.someArray, i32 0, i32 3, i32 1
call void @modifyExternal(ptr %gep3)
; Test that marking a value as external also marks all referenced values.
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
store i16 5, i16* @main.exposedValue1
call void @modifyExternal(ptr @main.exportedValue)
store i16 5, ptr @main.exposedValue1
; Test that marking a constant as external still allows loading from it.
call void @readExternal(i32* bitcast (i64* @main.exportedConst to i32*))
%constLoad = load i64, i64 * @main.exportedConst
call void @readExternal(ptr @main.exportedConst)
%constLoad = load i64, ptr @main.exportedConst
call void @runtime.printint64(i64 %constLoad)
; Test that this even propagates through functions.
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
call void @modifyExternal(ptr @willModifyGlobal)
store i16 7, ptr @main.exposedValue2
; Test that inline assembly is ignored.
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
call void @modifyExternal(ptr @hasInlineAsm)
; Test switch statement.
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
@@ -86,7 +86,7 @@ entry:
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
call void @runtime.printint64(i64 %elt)
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
store {i8, i32, {float, {i64, i16}}} %agg2, ptr @main.insertedValue
ret void
}
@@ -100,16 +100,16 @@ entry:
declare i64 @someValue()
declare void @modifyExternal(i32*)
declare void @modifyExternal(ptr)
declare void @readExternal(i32*)
declare void @readExternal(ptr)
; This function will modify an external value. By passing this function as a
; function pointer to an external function, @main.exposedValue2 should be
; marked as external.
define void @willModifyGlobal() {
entry:
store i16 8, i16* @main.exposedValue2
store i16 8, ptr @main.exposedValue2
ret void
}
+15 -15
View File
@@ -4,7 +4,7 @@ target triple = "x86_64--linux"
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
@main.nonConst2 = local_unnamed_addr global i64 0
@main.someArray = global [8 x { i16, i32 }] zeroinitializer
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exportedValue = global [1 x ptr] [ptr @main.exposedValue1]
@main.exportedConst = constant i64 42
@main.exposedValue1 = global i16 0
@main.exposedValue2 = local_unnamed_addr global i16 0
@@ -19,17 +19,17 @@ entry:
call void @runtime.printint64(i64 5)
call void @runtime.printnl()
%value1 = call i64 @someValue()
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0), align 8
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0), align 8
store i64 %value2, i64* @main.nonConst2, align 8
call void @modifyExternal(i32* bitcast (i8* getelementptr inbounds (i8, i8* bitcast ([8 x { i16, i32 }]* @main.someArray to i8*), i32 28) to i32*))
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
store i16 5, i16* @main.exposedValue1, align 2
call void @readExternal(i32* bitcast (i64* @main.exportedConst to i32*))
store i64 %value1, ptr @main.nonConst1, align 8
%value2 = load i64, ptr @main.nonConst1, align 8
store i64 %value2, ptr @main.nonConst2, align 8
call void @modifyExternal(ptr getelementptr inbounds (i8, ptr @main.someArray, i32 28))
call void @modifyExternal(ptr @main.exportedValue)
store i16 5, ptr @main.exposedValue1, align 2
call void @readExternal(ptr @main.exportedConst)
call void @runtime.printint64(i64 42)
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2, align 2
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
call void @modifyExternal(ptr @willModifyGlobal)
store i16 7, ptr @main.exposedValue2, align 2
call void @modifyExternal(ptr @hasInlineAsm)
call void @runtime.printint64(i64 6)
call void @runtime.printint64(i64 -1)
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
@@ -42,7 +42,7 @@ entry:
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue, align 8
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, ptr @main.insertedValue, align 8
ret void
}
@@ -55,13 +55,13 @@ entry:
declare i64 @someValue() local_unnamed_addr
declare void @modifyExternal(i32*) local_unnamed_addr
declare void @modifyExternal(ptr) local_unnamed_addr
declare void @readExternal(i32*) local_unnamed_addr
declare void @readExternal(ptr) local_unnamed_addr
define void @willModifyGlobal() {
entry:
store i16 8, i16* @main.exposedValue2, align 2
store i16 8, ptr @main.exposedValue2, align 2
ret void
}
+19 -11
View File
@@ -4,8 +4,9 @@ target triple = "x86_64--linux"
@intToPtrResult = global i8 0
@ptrToIntResult = global i8 0
@icmpResult = global i8 0
@pointerTagResult = global i64 0
@someArray = internal global {i16, i8, i8} zeroinitializer
@someArrayPointer = global i8* zeroinitializer
@someArrayPointer = global ptr zeroinitializer
define void @runtime.initAll() {
call void @main.init()
@@ -17,49 +18,56 @@ define internal void @main.init() {
call void @testPtrToInt()
call void @testConstGEP()
call void @testICmp()
call void @testPointerTag()
ret void
}
define internal void @testIntToPtr() {
%nil = icmp eq i8* inttoptr (i64 1024 to i8*), null
%nil = icmp eq ptr inttoptr (i64 1024 to ptr), null
br i1 %nil, label %a, label %b
a:
; should not be reached
store i8 1, i8* @intToPtrResult
store i8 1, ptr @intToPtrResult
ret void
b:
; should be reached
store i8 2, i8* @intToPtrResult
store i8 2, ptr @intToPtrResult
ret void
}
define internal void @testPtrToInt() {
%zero = icmp eq i64 ptrtoint (i8* @ptrToIntResult to i64), 0
%zero = icmp eq i64 ptrtoint (ptr @ptrToIntResult to i64), 0
br i1 %zero, label %a, label %b
a:
; should not be reached
store i8 1, i8* @ptrToIntResult
store i8 1, ptr @ptrToIntResult
ret void
b:
; should be reached
store i8 2, i8* @ptrToIntResult
store i8 2, ptr @ptrToIntResult
ret void
}
define internal void @testConstGEP() {
store i8* getelementptr inbounds (i8, i8* bitcast ({i16, i8, i8}* @someArray to i8*), i32 2), i8** @someArrayPointer
store ptr getelementptr inbounds (i8, ptr @someArray, i32 2), ptr @someArrayPointer
ret void
}
define internal void @testICmp() {
br i1 icmp eq (i64 ptrtoint (i8* @ptrToIntResult to i64), i64 0), label %equal, label %unequal
br i1 icmp eq (i64 ptrtoint (ptr @ptrToIntResult to i64), i64 0), label %equal, label %unequal
equal:
; should not be reached
store i8 1, i8* @icmpResult
store i8 1, ptr @icmpResult
ret void
unequal:
; should be reached
store i8 2, i8* @icmpResult
store i8 2, ptr @icmpResult
ret void
ret void
}
define internal void @testPointerTag() {
%val = and i64 ptrtoint (ptr getelementptr inbounds (i8, ptr @someArray, i32 2) to i64), 3
store i64 %val, ptr @pointerTagResult
ret void
}
+2 -1
View File
@@ -4,8 +4,9 @@ target triple = "x86_64--linux"
@intToPtrResult = local_unnamed_addr global i8 2
@ptrToIntResult = local_unnamed_addr global i8 2
@icmpResult = local_unnamed_addr global i8 2
@pointerTagResult = local_unnamed_addr global i64 2
@someArray = internal global { i16, i8, i8 } zeroinitializer
@someArrayPointer = local_unnamed_addr global i8* getelementptr inbounds ({ i16, i8, i8 }, { i16, i8, i8 }* @someArray, i64 0, i32 1)
@someArrayPointer = local_unnamed_addr global ptr getelementptr inbounds ({ i16, i8, i8 }, ptr @someArray, i64 0, i32 1)
define void @runtime.initAll() local_unnamed_addr {
ret void
+9 -9
View File
@@ -3,14 +3,14 @@ target triple = "x86_64--linux"
@main.v1 = global i1 0
@main.v2 = global i1 0
@"reflect/types.type:named:main.foo" = private constant { i8, i8*, i8* } { i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:main.foo", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4
@"reflect/types.type:pointer:named:main.foo" = external constant { i8, i8* }
@"reflect/types.type:named:main.foo" = private constant { i8, ptr, ptr } { i8 34, ptr @"reflect/types.type:pointer:named:main.foo", ptr @"reflect/types.type:basic:int" }, align 4
@"reflect/types.type:pointer:named:main.foo" = external constant { i8, ptr }
@"reflect/types.typeid:named:main.foo" = external constant i8
@"reflect/types.type:basic:int" = private constant { i8, i8* } { i8 2, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:int", i32 0, i32 0) }, align 4
@"reflect/types.type:pointer:basic:int" = external constant { i8, i8* }
@"reflect/types.type:basic:int" = private constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4
@"reflect/types.type:pointer:basic:int" = external constant { i8, ptr }
declare i1 @runtime.typeAssert(i8*, i8*, i8*, i8*)
declare i1 @runtime.typeAssert(ptr, ptr, ptr, ptr)
define void @runtime.initAll() unnamed_addr {
entry:
@@ -21,9 +21,9 @@ entry:
define internal void @main.init() unnamed_addr {
entry:
; Test type asserts.
%typecode = call i1 @runtime.typeAssert(i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:main.foo", i32 0, i32 0), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
store i1 %typecode, i1* @main.v1
%typecode2 = call i1 @runtime.typeAssert(i8* null, i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
store i1 %typecode2, i1* @main.v2
%typecode = call i1 @runtime.typeAssert(ptr @"reflect/types.type:named:main.foo", ptr @"reflect/types.typeid:named:main.foo", ptr undef, ptr null)
store i1 %typecode, ptr @main.v1
%typecode2 = call i1 @runtime.typeAssert(ptr null, ptr @"reflect/types.typeid:named:main.foo", ptr undef, ptr null)
store i1 %typecode2, ptr @main.v2
ret void
}
+2 -2
View File
@@ -25,7 +25,7 @@ for.loop:
br i1 %icmp, label %for.done, label %for.loop
for.done:
store i8 %a, i8* @main.phiNodesResultA
store i8 %b, i8* @main.phiNodesResultB
store i8 %a, ptr @main.phiNodesResultA
store i8 %b, ptr @main.phiNodesResultB
ret void
}
+34 -37
View File
@@ -3,7 +3,7 @@ target triple = "x86_64--linux"
declare void @externalCall(i64)
declare i64 @ptrHash(i8* nocapture)
declare i64 @ptrHash(ptr nocapture)
@foo.knownAtRuntime = global i64 0
@bar.knownAtRuntime = global i64 0
@@ -17,60 +17,60 @@ declare i64 @ptrHash(i8* nocapture)
define void @runtime.initAll() unnamed_addr {
entry:
call void @baz.init(i8* undef)
call void @foo.init(i8* undef)
call void @bar.init(i8* undef)
call void @main.init(i8* undef)
call void @x.init(i8* undef)
call void @y.init(i8* undef)
call void @z.init(i8* undef)
call void @baz.init(ptr undef)
call void @foo.init(ptr undef)
call void @bar.init(ptr undef)
call void @main.init(ptr undef)
call void @x.init(ptr undef)
call void @y.init(ptr undef)
call void @z.init(ptr undef)
ret void
}
define internal void @foo.init(i8* %context) unnamed_addr {
store i64 5, i64* @foo.knownAtRuntime
define internal void @foo.init(ptr %context) unnamed_addr {
store i64 5, ptr @foo.knownAtRuntime
unreachable ; this triggers a revert of @foo.init.
}
define internal void @bar.init(i8* %context) unnamed_addr {
%val = load i64, i64* @foo.knownAtRuntime
store i64 %val, i64* @bar.knownAtRuntime
define internal void @bar.init(ptr %context) unnamed_addr {
%val = load i64, ptr @foo.knownAtRuntime
store i64 %val, ptr @bar.knownAtRuntime
ret void
}
define internal void @baz.init(i8* %context) unnamed_addr {
define internal void @baz.init(ptr %context) unnamed_addr {
; Test extractvalue/insertvalue with more than one index.
%val = load [3 x {i64, i32}], [3 x {i64, i32}]* @baz.someGlobal
%val = load [3 x {i64, i32}], ptr @baz.someGlobal
%part = extractvalue [3 x {i64, i32}] %val, 0, 1
%val2 = insertvalue [3 x {i64, i32}] %val, i32 5, 2, 1
unreachable ; trigger revert
}
define internal void @main.init(i8* %context) unnamed_addr {
define internal void @main.init(ptr %context) unnamed_addr {
entry:
call void @externalCall(i64 3)
ret void
}
define internal void @x.init(i8* %context) unnamed_addr {
define internal void @x.init(ptr %context) unnamed_addr {
; Test atomic and volatile memory accesses.
store atomic i32 1, i32* @x.atomicNum seq_cst, align 4
%x = load atomic i32, i32* @x.atomicNum seq_cst, align 4
store i32 %x, i32* @x.atomicNum
%y = load volatile i32, i32* @x.volatileNum
store volatile i32 %y, i32* @x.volatileNum
store atomic i32 1, ptr @x.atomicNum seq_cst, align 4
%x = load atomic i32, ptr @x.atomicNum seq_cst, align 4
store i32 %x, ptr @x.atomicNum
%y = load volatile i32, ptr @x.volatileNum
store volatile i32 %y, ptr @x.volatileNum
ret void
}
define internal void @y.init(i8* %context) unnamed_addr {
define internal void @y.init(ptr %context) unnamed_addr {
entry:
br label %loop
loop:
; Test a wait-loop.
; This function must be reverted.
%val = load atomic i32, i32* @y.ready seq_cst, align 4
%val = load atomic i32, ptr @y.ready seq_cst, align 4
%ready = icmp eq i32 %val, 1
br i1 %ready, label %end, label %loop
@@ -78,27 +78,25 @@ end:
ret void
}
define internal void @z.init(i8* %context) unnamed_addr {
%bloom = bitcast i64* @z.bloom to i8*
define internal void @z.init(ptr %context) unnamed_addr {
; This can be safely expanded.
call void @z.setArr(i8* %bloom, i64 1, i8* %bloom)
call void @z.setArr(ptr @z.bloom, i64 1, ptr @z.bloom)
; This call should be reverted to prevent unrolling.
call void @z.setArr(i8* bitcast ([32 x i8]* @z.arr to i8*), i64 32, i8* %bloom)
call void @z.setArr(ptr @z.arr, i64 32, ptr @z.bloom)
ret void
}
define internal void @z.setArr(i8* %arr, i64 %n, i8* %context) unnamed_addr {
define internal void @z.setArr(ptr %arr, i64 %n, ptr %context) unnamed_addr {
entry:
br label %loop
loop:
%prev = phi i64 [ %n, %entry ], [ %idx, %loop ]
%idx = sub i64 %prev, 1
%elem = getelementptr i8, i8* %arr, i64 %idx
call void @z.set(i8* %elem, i8* %context)
%elem = getelementptr i8, ptr %arr, i64 %idx
call void @z.set(ptr %elem, ptr %context)
%done = icmp eq i64 %idx, 0
br i1 %done, label %end, label %loop
@@ -106,14 +104,13 @@ end:
ret void
}
define internal void @z.set(i8* %ptr, i8* %context) unnamed_addr {
define internal void @z.set(ptr %ptr, ptr %context) unnamed_addr {
; Insert the pointer into the Bloom filter.
%hash = call i64 @ptrHash(i8* %ptr)
%hash = call i64 @ptrHash(ptr %ptr)
%index = lshr i64 %hash, 58
%bit = shl i64 1, %index
%bloom = bitcast i8* %context to i64*
%old = load i64, i64* %bloom
%old = load i64, ptr %context
%new = or i64 %old, %bit
store i64 %new, i64* %bloom
store i64 %new, ptr %context
ret void
}
+27 -28
View File
@@ -13,41 +13,41 @@ target triple = "x86_64--linux"
declare void @externalCall(i64) local_unnamed_addr
declare i64 @ptrHash(i8* nocapture) local_unnamed_addr
declare i64 @ptrHash(ptr nocapture) local_unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call fastcc void @baz.init(i8* undef)
call fastcc void @foo.init(i8* undef)
%val = load i64, i64* @foo.knownAtRuntime, align 8
store i64 %val, i64* @bar.knownAtRuntime, align 8
call fastcc void @baz.init(ptr undef)
call fastcc void @foo.init(ptr undef)
%val = load i64, ptr @foo.knownAtRuntime, align 8
store i64 %val, ptr @bar.knownAtRuntime, align 8
call void @externalCall(i64 3)
store atomic i32 1, i32* @x.atomicNum seq_cst, align 4
%x = load atomic i32, i32* @x.atomicNum seq_cst, align 4
store i32 %x, i32* @x.atomicNum, align 4
%y = load volatile i32, i32* @x.volatileNum, align 4
store volatile i32 %y, i32* @x.volatileNum, align 4
call fastcc void @y.init(i8* undef)
call fastcc void @z.set(i8* bitcast (i64* @z.bloom to i8*), i8* bitcast (i64* @z.bloom to i8*))
call fastcc void @z.setArr(i8* getelementptr inbounds ([32 x i8], [32 x i8]* @z.arr, i32 0, i32 0), i64 32, i8* bitcast (i64* @z.bloom to i8*))
store atomic i32 1, ptr @x.atomicNum seq_cst, align 4
%x = load atomic i32, ptr @x.atomicNum seq_cst, align 4
store i32 %x, ptr @x.atomicNum, align 4
%y = load volatile i32, ptr @x.volatileNum, align 4
store volatile i32 %y, ptr @x.volatileNum, align 4
call fastcc void @y.init(ptr undef)
call fastcc void @z.set(ptr @z.bloom, ptr @z.bloom)
call fastcc void @z.setArr(ptr @z.arr, i64 32, ptr @z.bloom)
ret void
}
define internal fastcc void @foo.init(i8* %context) unnamed_addr {
store i64 5, i64* @foo.knownAtRuntime, align 8
define internal fastcc void @foo.init(ptr %context) unnamed_addr {
store i64 5, ptr @foo.knownAtRuntime, align 8
unreachable
}
define internal fastcc void @baz.init(i8* %context) unnamed_addr {
define internal fastcc void @baz.init(ptr %context) unnamed_addr {
unreachable
}
define internal fastcc void @y.init(i8* %context) unnamed_addr {
define internal fastcc void @y.init(ptr %context) unnamed_addr {
entry:
br label %loop
loop: ; preds = %loop, %entry
%val = load atomic i32, i32* @y.ready seq_cst, align 4
%val = load atomic i32, ptr @y.ready seq_cst, align 4
%ready = icmp eq i32 %val, 1
br i1 %ready, label %end, label %loop
@@ -55,15 +55,15 @@ end: ; preds = %loop
ret void
}
define internal fastcc void @z.setArr(i8* %arr, i64 %n, i8* %context) unnamed_addr {
define internal fastcc void @z.setArr(ptr %arr, i64 %n, ptr %context) unnamed_addr {
entry:
br label %loop
loop: ; preds = %loop, %entry
%prev = phi i64 [ %n, %entry ], [ %idx, %loop ]
%idx = sub i64 %prev, 1
%elem = getelementptr i8, i8* %arr, i64 %idx
call fastcc void @z.set(i8* %elem, i8* %context)
%elem = getelementptr i8, ptr %arr, i64 %idx
call fastcc void @z.set(ptr %elem, ptr %context)
%done = icmp eq i64 %idx, 0
br i1 %done, label %end, label %loop
@@ -71,13 +71,12 @@ end: ; preds = %loop
ret void
}
define internal fastcc void @z.set(i8* %ptr, i8* %context) unnamed_addr {
%hash = call i64 @ptrHash(i8* %ptr)
define internal fastcc void @z.set(ptr %ptr, ptr %context) unnamed_addr {
%hash = call i64 @ptrHash(ptr %ptr)
%index = lshr i64 %hash, 58
%bit = shl i64 1, %index
%bloom = bitcast i8* %context to i64*
%old = load i64, i64* %bloom, align 8
%old = load i64, ptr %context, align 8
%new = or i64 %old, %bit
store i64 %new, i64* %bloom, align 8
store i64 %new, ptr %context, align 8
ret void
}
}
+32 -35
View File
@@ -2,15 +2,15 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d"
@main.uint8SliceSrc = internal unnamed_addr global { i8*, i64, i64 } { i8* getelementptr inbounds ([2 x i8], [2 x i8]* @main.uint8SliceSrc.buf, i32 0, i32 0), i64 2, i64 2 }
@main.uint8SliceDst = internal unnamed_addr global { i8*, i64, i64 } zeroinitializer
@main.uint8SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.uint8SliceSrc.buf, i64 2, i64 2 }
@main.uint8SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer
@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024]
@main.int16SliceSrc = internal unnamed_addr global { i16*, i64, i64 } { i16* getelementptr inbounds ([3 x i16], [3 x i16]* @main.int16SliceSrc.buf, i32 0, i32 0), i64 3, i64 3 }
@main.int16SliceDst = internal unnamed_addr global { i16*, i64, i64 } zeroinitializer
@main.int16SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.int16SliceSrc.buf, i64 3, i64 3 }
@main.int16SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer
declare i64 @runtime.sliceCopy(i8* %dst, i8* %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
declare i64 @runtime.sliceCopy(ptr %dst, ptr %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
declare i8* @runtime.alloc(i64, i8*) unnamed_addr
declare ptr @runtime.alloc(i64, ptr) unnamed_addr
declare void @runtime.printuint8(i8)
@@ -25,23 +25,23 @@ entry:
define void @main() unnamed_addr {
entry:
; print(uintSliceSrc[0])
%uint8SliceSrc.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc, i64 0, i32 0)
%uint8SliceSrc.val = load i8, i8* %uint8SliceSrc.buf
%uint8SliceSrc.buf = load ptr, ptr @main.uint8SliceSrc
%uint8SliceSrc.val = load i8, ptr %uint8SliceSrc.buf
call void @runtime.printuint8(i8 %uint8SliceSrc.val)
; print(uintSliceDst[0])
%uint8SliceDst.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceDst, i64 0, i32 0)
%uint8SliceDst.val = load i8, i8* %uint8SliceDst.buf
%uint8SliceDst.buf = load ptr, ptr @main.uint8SliceDst
%uint8SliceDst.val = load i8, ptr %uint8SliceDst.buf
call void @runtime.printuint8(i8 %uint8SliceDst.val)
; print(int16SliceSrc[0])
%int16SliceSrc.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc, i64 0, i32 0)
%int16SliceSrc.val = load i16, i16* %int16SliceSrc.buf
%int16SliceSrc.buf = load ptr, ptr @main.int16SliceSrc
%int16SliceSrc.val = load i16, ptr %int16SliceSrc.buf
call void @runtime.printint16(i16 %int16SliceSrc.val)
; print(int16SliceDst[0])
%int16SliceDst.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceDst, i64 0, i32 0)
%int16SliceDst.val = load i16, i16* %int16SliceDst.buf
%int16SliceDst.buf = load ptr, ptr @main.int16SliceDst
%int16SliceDst.val = load i16, ptr %int16SliceDst.buf
call void @runtime.printint16(i16 %int16SliceDst.val)
ret void
}
@@ -50,37 +50,34 @@ define internal void @main.init() unnamed_addr {
entry:
; equivalent of:
; uint8SliceDst = make([]uint8, len(uint8SliceSrc))
%uint8SliceSrc = load { i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc
%uint8SliceSrc.len = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 1
%uint8SliceDst.buf = call i8* @runtime.alloc(i64 %uint8SliceSrc.len, i8* null)
%0 = insertvalue { i8*, i64, i64 } undef, i8* %uint8SliceDst.buf, 0
%1 = insertvalue { i8*, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
%2 = insertvalue { i8*, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
store { i8*, i64, i64 } %2, { i8*, i64, i64 }* @main.uint8SliceDst
%uint8SliceSrc = load { ptr, i64, i64 }, ptr @main.uint8SliceSrc
%uint8SliceSrc.len = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 1
%uint8SliceDst.buf = call ptr @runtime.alloc(i64 %uint8SliceSrc.len, ptr null)
%0 = insertvalue { ptr, i64, i64 } undef, ptr %uint8SliceDst.buf, 0
%1 = insertvalue { ptr, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
%2 = insertvalue { ptr, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
store { ptr, i64, i64 } %2, ptr @main.uint8SliceDst
; equivalent of:
; copy(uint8SliceDst, uint8SliceSrc)
%uint8SliceSrc.buf = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 0
%copy.n = call i64 @runtime.sliceCopy(i8* %uint8SliceDst.buf, i8* %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
%uint8SliceSrc.buf = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 0
%copy.n = call i64 @runtime.sliceCopy(ptr %uint8SliceDst.buf, ptr %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
; equivalent of:
; int16SliceDst = make([]int16, len(int16SliceSrc))
%int16SliceSrc = load { i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc
%int16SliceSrc.len = extractvalue { i16*, i64, i64 } %int16SliceSrc, 1
%int16SliceSrc = load { ptr, i64, i64 }, ptr @main.int16SliceSrc
%int16SliceSrc.len = extractvalue { ptr, i64, i64 } %int16SliceSrc, 1
%int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2
%int16SliceDst.buf.raw = call i8* @runtime.alloc(i64 %int16SliceSrc.len.bytes, i8* null)
%int16SliceDst.buf = bitcast i8* %int16SliceDst.buf.raw to i16*
%3 = insertvalue { i16*, i64, i64 } undef, i16* %int16SliceDst.buf, 0
%4 = insertvalue { i16*, i64, i64 } %3, i64 %int16SliceSrc.len, 1
%5 = insertvalue { i16*, i64, i64 } %4, i64 %int16SliceSrc.len, 2
store { i16*, i64, i64 } %5, { i16*, i64, i64 }* @main.int16SliceDst
%int16SliceDst.buf = call ptr @runtime.alloc(i64 %int16SliceSrc.len.bytes, ptr null)
%3 = insertvalue { ptr, i64, i64 } undef, ptr %int16SliceDst.buf, 0
%4 = insertvalue { ptr, i64, i64 } %3, i64 %int16SliceSrc.len, 1
%5 = insertvalue { ptr, i64, i64 } %4, i64 %int16SliceSrc.len, 2
store { ptr, i64, i64 } %5, ptr @main.int16SliceDst
; equivalent of:
; copy(int16SliceDst, int16SliceSrc)
%int16SliceSrc.buf = extractvalue { i16*, i64, i64 } %int16SliceSrc, 0
%int16SliceSrc.buf.i8ptr = bitcast i16* %int16SliceSrc.buf to i8*
%int16SliceDst.buf.i8ptr = bitcast i16* %int16SliceDst.buf to i8*
%copy.n2 = call i64 @runtime.sliceCopy(i8* %int16SliceDst.buf.i8ptr, i8* %int16SliceSrc.buf.i8ptr, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
%int16SliceSrc.buf = extractvalue { ptr, i64, i64 } %int16SliceSrc, 0
%copy.n2 = call i64 @runtime.sliceCopy(ptr %int16SliceDst.buf, ptr %int16SliceSrc.buf, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
ret void
}
+74 -57
View File
@@ -236,6 +236,9 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options
if testConfig.RunRegexp != "" {
flags = append(flags, "-test.run="+testConfig.RunRegexp)
}
if testConfig.SkipRegexp != "" {
flags = append(flags, "-test.skip="+testConfig.SkipRegexp)
}
if testConfig.BenchRegexp != "" {
flags = append(flags, "-test.bench="+testConfig.BenchRegexp)
}
@@ -248,6 +251,9 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options
if testConfig.Count != nil && *testConfig.Count != 1 {
flags = append(flags, "-test.count="+strconv.Itoa(*testConfig.Count))
}
if testConfig.Shuffle != "" {
flags = append(flags, "-test.shuffle="+testConfig.Shuffle)
}
logToStdout := testConfig.Verbose || testConfig.BenchRegexp != ""
@@ -342,7 +348,7 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options
fmt.Fprintf(w, "? \t%s\t[no test files]\n", err.ImportPath)
// Pretend the test passed - it at least didn't fail.
return true, nil
} else if passed {
} else if passed && !testConfig.CompileOnly {
fmt.Fprintf(w, "ok \t%s\t%.3fs\n", importPath, duration.Seconds())
} else {
fmt.Fprintf(w, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
@@ -526,7 +532,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return fmt.Errorf("unknown flash method: %s", flashMethod)
}
if options.Monitor {
return Monitor("", options)
return Monitor(result.Executable, "", options)
}
return nil
}
@@ -939,28 +945,29 @@ func touchSerialPortAt1200bps(port string) (err error) {
return fmt.Errorf("opening port: %s", err)
}
func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) error {
func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error {
// find standard UF2 info path
var infoPath string
switch runtime.GOOS {
case "linux", "freebsd":
fi, err := os.Stat("/run/media")
if err != nil || !fi.IsDir() {
infoPath = "/media/*/" + volume + "/INFO_UF2.TXT"
} else {
infoPath = "/run/media/*/" + volume + "/INFO_UF2.TXT"
infoPaths := make([]string, 0, len(volumes))
for _, volume := range volumes {
switch runtime.GOOS {
case "linux", "freebsd":
fi, err := os.Stat("/run/media")
if err != nil || !fi.IsDir() {
infoPaths = append(infoPaths, "/media/*/"+volume+"/INFO_UF2.TXT")
} else {
infoPaths = append(infoPaths, "/run/media/*/"+volume+"/INFO_UF2.TXT")
}
case "darwin":
infoPaths = append(infoPaths, "/Volumes/"+volume+"/INFO_UF2.TXT")
case "windows":
path, err := windowsFindUSBDrive(volume, options)
if err == nil {
infoPaths = append(infoPaths, path+"/INFO_UF2.TXT")
}
}
case "darwin":
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
case "windows":
path, err := windowsFindUSBDrive(volume, options)
if err != nil {
return err
}
infoPath = path + "/INFO_UF2.TXT"
}
d, err := locateDevice(volume, infoPath, options.Timeout)
d, err := locateDevice(volumes, infoPaths, options.Timeout)
if err != nil {
return err
}
@@ -968,28 +975,29 @@ func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) erro
return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2")
}
func flashHexUsingMSD(volume, tmppath string, options *compileopts.Options) error {
func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error {
// find expected volume path
var destPath string
switch runtime.GOOS {
case "linux", "freebsd":
fi, err := os.Stat("/run/media")
if err != nil || !fi.IsDir() {
destPath = "/media/*/" + volume
} else {
destPath = "/run/media/*/" + volume
destPaths := make([]string, 0, len(volumes))
for _, volume := range volumes {
switch runtime.GOOS {
case "linux", "freebsd":
fi, err := os.Stat("/run/media")
if err != nil || !fi.IsDir() {
destPaths = append(destPaths, "/media/*/"+volume)
} else {
destPaths = append(destPaths, "/run/media/*/"+volume)
}
case "darwin":
destPaths = append(destPaths, "/Volumes/"+volume)
case "windows":
path, err := windowsFindUSBDrive(volume, options)
if err == nil {
destPaths = append(destPaths, path+"/")
}
}
case "darwin":
destPath = "/Volumes/" + volume
case "windows":
path, err := windowsFindUSBDrive(volume, options)
if err != nil {
return err
}
destPath = path + "/"
}
d, err := locateDevice(volume, destPath, options.Timeout)
d, err := locateDevice(volumes, destPaths, options.Timeout)
if err != nil {
return err
}
@@ -997,21 +1005,28 @@ func flashHexUsingMSD(volume, tmppath string, options *compileopts.Options) erro
return moveFile(tmppath, d+"/flash.hex")
}
func locateDevice(volume, path string, timeout time.Duration) (string, error) {
func locateDevice(volumes, paths []string, timeout time.Duration) (string, error) {
var d []string
var err error
for start := time.Now(); time.Since(start) < timeout; {
d, err = filepath.Glob(path)
if err != nil {
return "", err
for _, path := range paths {
d, err = filepath.Glob(path)
if err != nil {
return "", err
}
if d != nil {
break
}
}
if d != nil {
break
}
time.Sleep(500 * time.Millisecond)
}
if d == nil {
return "", errors.New("unable to locate device: " + volume)
return "", errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]")
}
return d[0], nil
}
@@ -1386,9 +1401,6 @@ func main() {
serial := flag.String("serial", "", "which serial output to use (none, uart, usb)")
work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit")
interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
var tags buildutil.TagsFlag
flag.Var(&tags, "tags", "a space-separated list of extra build tags")
target := flag.String("target", "", "chip/board name or JSON target specification file")
@@ -1415,6 +1427,17 @@ func main() {
monitor := flag.Bool("monitor", false, "enable serial monitor")
baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor")
// Internal flags, that are only intended for TinyGo development.
printIR := flag.Bool("internal-printir", false, "print LLVM IR")
dumpSSA := flag.Bool("internal-dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("internal-verifyir", false, "run extra verification steps on LLVM IR")
// Don't generate debug information in the IR, to make IR more readable.
// You generally want debug information in IR for various features, like
// stack size calculation and features like -size=short, -print-allocs=,
// etc. The -no-debug flag is used to strip it at link time. But for TinyGo
// development it can be useful to not emit debug information at all.
skipDwarf := flag.Bool("internal-nodwarf", false, "internal flag, use -no-debug instead")
var flagJSON, flagDeps, flagTest bool
if command == "help" || command == "list" || command == "info" || command == "build" {
flag.BoolVar(&flagJSON, "json", false, "print data in JSON format")
@@ -1434,10 +1457,12 @@ func main() {
flag.BoolVar(&testConfig.Verbose, "v", false, "verbose: print additional output")
flag.BoolVar(&testConfig.Short, "short", false, "short: run smaller test suite to save time")
flag.StringVar(&testConfig.RunRegexp, "run", "", "run: regexp of tests to run")
flag.StringVar(&testConfig.SkipRegexp, "skip", "", "skip: regexp of tests to skip")
testConfig.Count = flag.Int("count", 1, "count: number of times to run tests/benchmarks `count` times")
flag.StringVar(&testConfig.BenchRegexp, "bench", "", "run: regexp of benchmarks to run")
flag.StringVar(&testConfig.BenchRegexp, "bench", "", "bench: regexp of benchmarks to run")
flag.StringVar(&testConfig.BenchTime, "benchtime", "", "run each benchmark for duration `d`")
flag.BoolVar(&testConfig.BenchMem, "benchmem", false, "show memory stats for benchmarks")
flag.StringVar(&testConfig.Shuffle, "shuffle", "", "shuffle the order the tests and benchmarks run")
}
// Early command processing, before commands are interpreted by the Go flag
@@ -1489,6 +1514,7 @@ func main() {
PrintIR: *printIR,
DumpSSA: *dumpSSA,
VerifyIR: *verifyIR,
SkipDWARF: *skipDwarf,
Semaphore: make(chan struct{}, *parallelism),
Debug: !*nodebug,
PrintSizes: *printSize,
@@ -1530,15 +1556,6 @@ func main() {
defer pprof.StopCPUProfile()
}
// Limit the number of threads to one.
// This is an attempted workaround for the crashes we're seeing in CI on
// Windows. If this change helps, it indicates there is a concurrency issue.
// If it doesn't, then there is something else going on. Either way, this
// should be removed once the test is done.
if runtime.GOOS == "windows" {
runtime.GOMAXPROCS(1)
}
switch command {
case "build":
pkgName := "."
@@ -1703,7 +1720,7 @@ func main() {
os.Exit(1)
}
case "monitor":
err := Monitor(*port, options)
err := Monitor("", *port, options)
handleCompilerError(err)
case "targets":
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
+134 -5
View File
@@ -1,9 +1,18 @@
package main
import (
"debug/dwarf"
"debug/elf"
"debug/macho"
"debug/pe"
"errors"
"fmt"
"go/token"
"io"
"os"
"os/signal"
"regexp"
"strconv"
"time"
"github.com/mattn/go-tty"
@@ -13,7 +22,7 @@ import (
)
// Monitor connects to the given port and reads/writes the serial port.
func Monitor(port string, options *compileopts.Options) error {
func Monitor(executable, port string, options *compileopts.Options) error {
config, err := builder.NewConfig(options)
if err != nil {
return err
@@ -74,17 +83,31 @@ func Monitor(port string, options *compileopts.Options) error {
go func() {
buf := make([]byte, 100*1024)
var line []byte
for {
n, err := p.Read(buf)
if err != nil {
errCh <- fmt.Errorf("read error: %w", err)
return
}
if n == 0 {
continue
start := 0
for i, c := range buf[:n] {
if c == '\n' {
os.Stdout.Write(buf[start : i+1])
start = i + 1
address := extractPanicAddress(line)
if address != 0 {
loc, err := addressToLine(executable, address)
if err == nil && loc.IsValid() {
fmt.Printf("[tinygo: panic at %s]\n", loc.String())
}
}
line = line[:0]
} else {
line = append(line, c)
}
}
fmt.Printf("%v", string(buf[:n]))
os.Stdout.Write(buf[start:n])
}
}()
@@ -104,3 +127,109 @@ func Monitor(port string, options *compileopts.Options) error {
return <-errCh
}
var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `)
// Extract the address from the "panic: runtime error at" message.
func extractPanicAddress(line []byte) uint64 {
matches := addressMatch.FindSubmatch(line)
if matches != nil {
address, err := strconv.ParseUint(string(matches[1]), 16, 64)
if err == nil {
return address
}
}
return 0
}
// Convert an address in the binary to a source address location.
func addressToLine(executable string, address uint64) (token.Position, error) {
data, err := readDWARF(executable)
if err != nil {
return token.Position{}, err
}
r := data.Reader()
for {
e, err := r.Next()
if err != nil {
return token.Position{}, err
}
if e == nil {
break
}
switch e.Tag {
case dwarf.TagCompileUnit:
r.SkipChildren()
lr, err := data.LineReader(e)
if err != nil {
return token.Position{}, err
}
var lineEntry = dwarf.LineEntry{
EndSequence: true,
}
for {
// Read the next .debug_line entry.
prevLineEntry := lineEntry
err := lr.Next(&lineEntry)
if err != nil {
if err == io.EOF {
break
}
return token.Position{}, err
}
if prevLineEntry.EndSequence && lineEntry.Address == 0 {
// Tombstone value. This symbol has been removed, for
// example by the --gc-sections linker flag. It is still
// here in the debug information because the linker can't
// just remove this reference.
// Read until the next EndSequence so that this sequence is
// skipped.
// For more details, see (among others):
// https://reviews.llvm.org/D84825
for {
err := lr.Next(&lineEntry)
if err != nil {
return token.Position{}, err
}
if lineEntry.EndSequence {
break
}
}
}
if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to
// lineEntry.
if prevLineEntry.Address <= address && lineEntry.Address > address {
return token.Position{
Filename: prevLineEntry.File.Name,
Line: prevLineEntry.Line,
Column: prevLineEntry.Column,
}, nil
}
}
}
}
}
return token.Position{}, nil // location not found
}
// Read the DWARF debug information from a given file (in various formats).
func readDWARF(executable string) (*dwarf.Data, error) {
f, err := os.Open(executable)
if err != nil {
return nil, err
}
if file, err := elf.NewFile(f); err == nil {
return file.DWARF()
} else if file, err := macho.NewFile(f); err == nil {
return file.DWARF()
} else if file, err := pe.NewFile(f); err == nil {
return file.DWARF()
} else {
return nil, errors.New("unknown binary format")
}
}
+66
View File
@@ -0,0 +1,66 @@
package main
import (
"bytes"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts"
)
func TestTraceback(t *testing.T) {
if runtime.GOOS != "linux" {
// We care about testing the ELF format, which is only used on Linux
// (not on MacOS or Windows).
t.Skip("Test only works on Linux")
}
// Build a small binary that only panics.
tmpdir := t.TempDir()
config, err := builder.NewConfig(&compileopts.Options{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
Opt: "z",
InterpTimeout: time.Minute,
Debug: true,
})
if err != nil {
t.Fatal(err)
}
result, err := builder.Build("testdata/trivialpanic.go", ".elf", tmpdir, config)
if err != nil {
t.Fatal(err)
}
// Run this binary, and capture the output.
buf := &bytes.Buffer{}
cmd := exec.Command(result.Binary)
cmd.Stdout = buf
cmd.Stderr = buf
cmd.Run() // this will return an error because of the panic, ignore it
// Extract the "runtime error at" address.
line := bytes.TrimSpace(buf.Bytes())
address := extractPanicAddress(line)
if address == 0 {
t.Fatalf("could not extract panic address from %#v", string(line))
}
// Look up the source location for this address.
location, err := addressToLine(result.Executable, address)
if err != nil {
t.Fatal("could not read source location:", err)
}
// Verify that the source location is as expected.
if filepath.Base(location.Filename) != "trivialpanic.go" {
t.Errorf("expected path to end with trivialpanic.go, got %#v", location.Filename)
}
if location.Line != 6 {
t.Errorf("expected panic location to be line 6, got line %d", location.Line)
}
}
+374 -21
View File
@@ -51,20 +51,39 @@ var (
GRAPHICS = (*GRAPHICS_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x004C)))
// GBA Sound Channel 1 - Tone & Sweep
SOUND1 = (*SOUND_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0060)))
SOUND1 = (*SOUND1_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0060)))
// GBA Sound Channel 2 - Tone
SOUND2 = (*SOUND_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0068)))
SOUND2 = (*SOUND2_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0068)))
// TODO: Sound channel 3 and 4
// GBA Sound Channel 3 - Wave Output
SOUND3 = (*SOUND3_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0070)))
// GBA Sound Channel 4 - Noise
SOUND4 = (*SOUND4_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0078)))
// GBA Sound Control
SOUND = (*SOUND_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0080)))
DMA0 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00B0)))
DMA1 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00BC)))
DMA2 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00C8)))
DMA3 = (*DMA_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x00D4)))
TM0 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0100)))
TM1 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0104)))
TM2 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0108)))
TM3 = (*TIMER_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x010C)))
// Communication 1
SIODATA32 = (*SIODATA32_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0120)))
SIOMULTI = (*SIOMULTI_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0120)))
KEY = (*KEY_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0130)))
// Communication 2
SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134)))
INTERRUPT = (*INTERRUPT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0200)))
)
@@ -119,8 +138,11 @@ const (
// Screenblock size
SBB_SIZE = 0x00800
// BG VRAM_MODE_0_2 size
VRAM_BG_SIZE_MODE_0_2 = 0x10000
// BG VRAM size
VRAM_BG_SIZE = 0x10000
VRAM_BG_SIZE_MODE_3_5 = 0x14000
// Object VRAM size
VRAM_OBJ_SIZE = 0x08000
@@ -154,8 +176,11 @@ var (
// Back page address
MEM_VRAM_BACK = MEM_VRAM + VRAM_PAGE_SIZE
// Object VRAM address
MEM_VRAM_OBJ = MEM_VRAM + VRAM_BG_SIZE
// Object VRAM address - BG Mode 0-2
MEM_VRAM_OBJ_MODE0_2 = MEM_VRAM + VRAM_BG_SIZE_MODE_0_2
// Object VRAM address - BG Mode 3-5
MEM_VRAM_OBJ_MODE_3_5 = MEM_VRAM + VRAM_BG_SIZE_MODE_3_5
)
// Display registers
@@ -221,7 +246,7 @@ type GRAPHICS_Type struct {
BLDY volatile.Register16
}
type SOUND_Type struct {
type SOUND1_Type struct {
// Sweep register
CNT_L volatile.Register16
@@ -232,7 +257,59 @@ type SOUND_Type struct {
CNT_X volatile.Register16
}
// TODO: DMA
type SOUND2_Type struct {
// Duty/Len/Envelope
CNT_L volatile.Register16
// not used
_ volatile.Register16
// Frequency/Control
CNT_H volatile.Register16
}
type SOUND3_Type struct {
// Stop/Wave RAM select
CNT_L volatile.Register16
// Length/Volume
CNT_H volatile.Register16
// Frequency/Control
CNT_X volatile.Register16
}
type SOUND4_Type struct {
// Length/Envelope
CNT_L volatile.Register16
// not used
_ volatile.Register16
// Frequency/Control
CNT_H volatile.Register16
}
type SOUND_Type struct {
// Control Stereo/Volume/Enable
CNT_L volatile.Register16
// Control Mixing/DMA Control
CNT_H volatile.Register16
// Control Sound on/off
CNT_X volatile.Register16
}
// DMA
type DMA_Type struct {
SAD_L volatile.Register16
SAD_H volatile.Register16
DAD_L volatile.Register16
DAD_H volatile.Register16
CNT_L volatile.Register16
CNT_H volatile.Register16
}
// TIMER
type TIMER_Type struct {
@@ -240,7 +317,28 @@ type TIMER_Type struct {
CNT volatile.Register16
}
// TODO: serial
// serial
type SIODATA32_Type struct {
DATA32_L volatile.Register16
DATA32_H volatile.Register16
_ volatile.Register16
_ volatile.Register16
CNT volatile.Register16
DATA8 volatile.Register16
}
type SIOMULTI_Type struct {
MULTI0 volatile.Register16
MULTI1 volatile.Register16
MULTI2 volatile.Register16
MULTI3 volatile.Register16
CNT volatile.Register16
MLT_SEND volatile.Register16
}
type SIO_Type struct {
RCNT volatile.Register16
}
// Keypad registers
type KEY_Type struct {
@@ -259,6 +357,34 @@ type INTERRUPT_Type struct {
PAUSE volatile.Register16
}
// LCD OBJ Attributes
type OAMOBJ_Type struct {
ATT0 volatile.Register16
ATT1 volatile.Register16
ATT2 volatile.Register16
_ volatile.Register16
}
// OAM Rotation/Scaling Parameters
type OAMROT_Type struct {
_ volatile.Register16
_ volatile.Register16
_ volatile.Register16
PA volatile.Register16
_ volatile.Register16
_ volatile.Register16
_ volatile.Register16
PB volatile.Register16
_ volatile.Register16
_ volatile.Register16
_ volatile.Register16
PC volatile.Register16
_ volatile.Register16
_ volatile.Register16
_ volatile.Register16
PD volatile.Register16
}
// Constants for DISP: display
const (
// BGMODE: background mode.
@@ -372,6 +498,40 @@ const (
DISPSTAT_VCOUNT_SETTING_Pos = 0x8
)
const (
BGCNT_PRIORITY_Pos = 0x0
BGCNT_PRIORITY_Msk = 0x3
BGCNT_CHAR_BASE_Pos = 0x2
BGCNT_CHAR_BASE_Msk = 0x3
BGCNT_MOSAIC_Pos = 0x6
BGCNT_MOSAIC_DISABLE = 0x0
BGCNT_MOSAIC_ENABLE = 0x1
BGCNT_COLORS_Pos = 0x7
BGCNT_COLORS_16 = 0x0
BGCNT_COLORS_256 = 0x1
BGCNT_BASE_Pos = 0x8
BGCNT_BASE_Msk = 0x1F
BGCNT_OVERFLOW_Pos = 0xD
BGCNT_OVERFLOW_TRANS = 0x0
BGCNT_OVERFLOW_WRAP = 0x1
BGCNT_SIZE_Pos = 0xE
BGCNT_SIZE_Msk = 0x3
)
const (
BG_HOFS_Pos = 0x0
BG_HOFS_Msk = 0x1FF
BG_VOFS_Pos = 0x0
BG_VOFS_Msk = 0x1FF
)
// Constants for TIMER
const (
// PRESCALER: Prescaler Selection (0=F/1, 1=F/64, 2=F/256, 3=F/1024)
@@ -403,6 +563,135 @@ const (
TIMERCNT_TIMER_STOP = 0x0
)
const (
// normal mode
SIOCNT_NORMAL_SC_Pos = 0x0
SIOCNT_NORMAL_SC_INTERNAL = 0x1
SIOCNT_NORMAL_SC_EXTERNAL = 0x0
SIOCNT_NORMAL_SCSPEED_Pos = 0x1
SIOCNT_NORMAL_SCSPEED_256K = 0x0
SIOCNT_NORMAL_SCSPEED_2M = 0x1
SIOCNT_NORMAL_SCSTATE_Pos = 0x2
SIOCNT_NORMAL_SCSTATE_LOW = 0x0
SIOCNT_NORMAL_SCSTATE_HIGH = 0x1
SIOCNT_NORMAL_SO_INACTIVE_Pos = 0x3
SIOCNT_NORMAL_SO_INACTIVE_LOW = 0x0
SIOCNT_NORMAL_SO_INACTIVE_HIGH = 0x1
SIOCNT_NORMAL_START_Pos = 0x7
SIOCNT_NORMAL_START_READY = 0x0
SIOCNT_NORMAL_START_ACTIVE = 0x1
SIOCNT_NORMAL_LEN_Pos = 0xC
SIOCNT_NORMAL_LEN8 = 0x0
SIOCNT_NORMAL_LEN32 = 0x1
SIOCNT_NORMAL_MODE_Pos = 0xD
SIOCNT_NORMAL_MODE = 0x0
// multiplayer mode
SIOCNT_MULTI_BR_Pos = 0x0
SIOCNT_MULTI_BR_Msk = 0x3
SIOCNT_MULTI_BR_9600 = 0x0
SIOCNT_MULTI_BR_38400 = 0x1
SIOCNT_MULTI_BR_57600 = 0x2
SIOCNT_MULTI_BR_115200 = 0x3
SIOCNT_MULTI_SI_Pos = 0x2
SIOCNT_MULTI_SI_PARENT = 0x0
SIOCNT_MULTI_SI_CHILD = 0x1
SIOCNT_MULTI_SD_Pos = 0x3
SIOCNT_MULTI_SD_BAD = 0x0
SIOCNT_MULTI_SD_READY = 0x1
SIOCNT_MULTI_ID_Pos = 0x4
SIOCNT_MULTI_ID_Msk = 0x3
SIOCNT_MULTI_ID_PARENT = 0x0
SIOCNT_MULTI_ID_CHILD1 = 0x1
SIOCNT_MULTI_ID_CHILD2 = 0x2
SIOCNT_MULTI_ID_CHILD3 = 0x3
SIOCNT_MULTI_ERR_Pos = 0x6
SIOCNT_MULTI_ERR_NORMAL = 0x0
SIOCNT_MULTI_ERR_ERROR = 0x1
SIOCNT_MULTI_STARTBUSY_Pos = 0x7
SIOCNT_MULTI_STARTBUSY_INACTIVE = 0x0
SIOCNT_MULTI_STARTBUSY_STARTBUSY = 0x1
SIOCNT_MULTI_MODE_Pos = 0xC
SIOCNT_MULTI_MODE_Msk = 0x3
SIOCNT_MULTI_MODE = 0x01
// uart mode
SIOCNT_UART_BR_Pos = 0x0
SIOCNT_UART_BR_Msk = 0x3
SIOCNT_UART_BR_9600 = 0x0
SIOCNT_UART_BR_38400 = 0x1
SIOCNT_UART_BR_57600 = 0x2
SIOCNT_UART_BR_115200 = 0x3
SIOCNT_UART_CTS_Pos = 0x2
SIOCNT_UART_CTS_ALWAYS = 0x0
SIOCNT_UART_CTS_SENDLOW = 0x1
SIOCNT_UART_PARITY_Pos = 0x3
SIOCNT_UART_PARITY_EVEN = 0x0
SIOCNT_UART_PARITY_ODD = 0x1
SIOCNT_UART_SEND_Pos = 0x4
SIOCNT_UART_SEND_NOTFULL = 0x0
SIOCNT_UART_SEND_FULL = 0x1
SIOCNT_UART_REC_Pos = 0x5
SIOCNT_UART_REC_NOTEMPTY = 0x0
SIOCNT_UART_REC_EMPTY = 0x1
SIOCNT_UART_ERR_Pos = 0x6
SIOCNT_UART_ERR_NOERROR = 0x0
SIOCNT_UART_ERR_ERROR = 0x1
SIOCNT_UART_DATALEN_Pos = 0x7
SIOCNT_UART_DATALEN_7 = 0x0
SIOCNT_UART_DATALEN_8 = 0x1
SIOCNT_UART_FIFO_Pos = 0x8
SIOCNT_UART_FIFO_DISABLE = 0x0
SIOCNT_UART_FIFO_ENABLE = 0x1
SIOCNT_UART_PARITY_ENABLE_Pos = 0x9
SIOCNT_UART_PARITY_DISABLE = 0x0
SIOCNT_UART_PARITY_ENABLE = 0x1
SIOCNT_UART_SEND_ENABLE_Pos = 0xA
SIOCNT_UART_SEND_DISABLE = 0x0
SIOCNT_UART_SEND_ENABLE = 0x1
SIOCNT_UART_REC_ENABLE_Pos = 0xB
SIOCNT_UART_REC_DISABLE = 0x0
SIOCNT_UART_REC_ENABLE = 0x1
SIOCNT_UART_MODE_Pos = 0xC
SIOCNT_UART_MODE_Msk = 0x3
SIOCNT_UART_MODE = 0x11
// IRQs used by all
SIOCNT_IRQ_Pos = 0xE
SIOCNT_IRQ_DISABLE = 0x0
SIOCNT_IRQ_ENABLE = 0x1
)
const (
SIO_RCNT_MODE_Pos = 0xF
SIO_RCNT_MODE_NORMAL = 0x0
SIO_RCNT_MODE_MULTI = 0x0
SIO_RCNT_MODE_UART = 0x0
)
// Constants for KEY
const (
// KEYINPUT
@@ -420,18 +709,24 @@ const (
KEYINPUT_BUTTON_L_Pos = 0x9
// KEYCNT
KEYCNT_IGNORE = 0x0
KEYCNT_SELECT = 0x1
KEYCNT_BUTTON_A_Pos = 0x0
KEYCNT_BUTTON_B_Pos = 0x1
KEYCNT_BUTTON_SELECT_Pos = 0x2
KEYCNT_BUTTON_START_Pos = 0x3
KEYCNT_BUTTON_RIGHT_Pos = 0x4
KEYCNT_BUTTON_LEFT_Pos = 0x5
KEYCNT_BUTTON_UP_Pos = 0x6
KEYCNT_BUTTON_DOWN_Pos = 0x7
KEYCNT_BUTTON_R_Pos = 0x8
KEYCNT_BUTTON_L_Pos = 0x9
KEYCNT_IGNORE = 0x0
KEYCNT_SELECT = 0x1
KEYCNT_BUTTON_A_Pos = 0x0
KEYCNT_BUTTON_B_Pos = 0x1
KEYCNT_BUTTON_SELECT_Pos = 0x2
KEYCNT_BUTTON_START_Pos = 0x3
KEYCNT_BUTTON_RIGHT_Pos = 0x4
KEYCNT_BUTTON_LEFT_Pos = 0x5
KEYCNT_BUTTON_UP_Pos = 0x6
KEYCNT_BUTTON_DOWN_Pos = 0x7
KEYCNT_BUTTON_R_Pos = 0x8
KEYCNT_BUTTON_L_Pos = 0x9
KEYCNT_BUTTON_IRQ_DISABLE = 0x0
KEYCNT_BUTTON_IRQ_ENABLE = 0x1
KEYCNT_BUTTON_IRQ_ENABLE_Pos = 0xE
KEYCNT_BUTTON_IRQ_COND_OR = 0x0
KEYCNT_BUTTON_IRQ_COND_AND = 0x1
KEYCNT_BUTTON_IRQ_COND_Pos = 0xF
)
// Constants for INTERRUPT
@@ -472,3 +767,61 @@ const (
INTERRUPT_IF_KEYPAD_Pos = 0xC
INTERRUPT_IF_GAMPAK_Pos = 0xD
)
const (
OAMOBJ_ATT0_Y_Pos = 0x0
OAMOBJ_ATT0_Y_Msk = 0xFF
OAMOBJ_ATT0_OM_Pos = 0x8
OAMOBJ_ATT0_OM_Msk = 0x3
OAMOBJ_ATT0_OM_REG = 0x0
OAMOBJ_ATT0_OM_AFF = 0x1
OAMOBJ_ATT0_OM_HIDE = 0x2
OAMOBJ_ATT0_OM_DBL = 0x3
OAMOBJ_ATT0_GM_Pos = 0xA
OAMOBJ_ATT0_GM_Msk = 0x3
OAMOBJ_ATT0_GM_REG = 0x0
OAMOBJ_ATT0_GM_BLEND = 0x1
OAMOBJ_ATT0_GM_WIN = 0x2
OAMOBJ_ATT0_MOSAIC_Pos = 0xC
OAMOBJ_ATT0_MOSAIC_DISABLE = 0x0
OAMOBJ_ATT0_MOSAIC_ENABLE = 0x1
OAMOBJ_ATT0_COLOR_Pos = 0xD
OAMOBJ_ATT0_COLOR_4BPP = 0x0
OAMOBJ_ATT0_COLOR_8BPP = 0x1
OAMOBJ_ATT0_SH_Pos = 0xE
OAMOBJ_ATT0_SH_Msk = 0x3
OAMOBJ_ATT0_SH_SQUARE = 0x0
OAMOBJ_ATT0_SH_WIDE = 0x1
OAMOBJ_ATT0_SH_TALL = 0x2
OAMOBJ_ATT1_X_Pos = 0x0
OAMOBJ_ATT1_X_Msk = 0xFF
OAMOBJ_ATT1_AID_Pos = 0x9
OAMOBJ_ATT1_AID_Msk = 0x1F
OAMOBJ_ATT1_HF_Pos = 0xC
OAMOBJ_ATT1_HF_NOFLIP = 0x0
OAMOBJ_ATT1_HF_FLIP = 0x1
OAMOBJ_ATT1_VF_Pos = 0xD
OAMOBJ_ATT1_VF_NOFLIP = 0x0
OAMOBJ_ATT1_VF_FLIP = 0x1
OAMOBJ_ATT1_SZ_Pos = 0xE
OAMOBJ_ATT1_SZ_Msk = 0x3
OAMOBJ_ATT2_TID_Pos = 0x0
OAMOBJ_ATT2_TID_Msk = 0xFF
OAMOBJ_ATT2_PR_Pos = 0xA
OAMOBJ_ATT2_PR_Msk = 0x3
OAMOBJ_ATT2_PB_Pos = 0xC
OAMOBJ_ATT2_PB_Msk = 0xF
)
+11
View File
@@ -0,0 +1,11 @@
package main
import "time"
// This is used for smoke tests for chips without an associated board.
func main() {
for {
time.Sleep(time.Second)
}
}
+38 -11
View File
@@ -7,7 +7,10 @@ import (
var (
err error
message = "1234567887654321123456788765432112345678876543211234567887654321"
message = "1234567887654321123456788765432112345678876543211234567887654321" +
"1234567887654321123456788765432112345678876543211234567887654321" +
"1234567887654321123456788765432112345678876543211234567887654321" +
"1234567887654321123456788765432112345678876543211234567887654321"
)
func main() {
@@ -21,30 +24,54 @@ func main() {
println("Flash erase block size:", machine.Flash.EraseBlockSize())
println()
flash := machine.OpenFlashBuffer(machine.Flash, machine.FlashDataStart())
original := make([]byte, len(message))
saved := make([]byte, len(message))
// Read flash contents on start (data shall survive power off)
print("Reading data from flash: ")
_, err = flash.Read(original)
println("Reading original data from flash:")
_, err = machine.Flash.ReadAt(original, 0)
checkError(err)
println(string(original))
// erase flash
println("Erasing flash...")
needed := int64(len(message)) / machine.Flash.EraseBlockSize()
if needed == 0 {
// have to erase at least 1 block
needed = 1
}
err := machine.Flash.EraseBlocks(0, needed)
checkError(err)
// Write the message to flash
print("Writing data to flash: ")
flash.Seek(0, 0) // rewind back to beginning
_, err = flash.Write([]byte(message))
println("Writing new data to flash:")
_, err = machine.Flash.WriteAt([]byte(message), 0)
checkError(err)
println(string(message))
// Read back flash contents after write (verify data is the same as written)
print("Reading data back from flash: ")
flash.Seek(0, 0) // rewind back to beginning
_, err = flash.Read(saved)
println("Reading data back from flash: ")
_, err = machine.Flash.ReadAt(saved, 0)
checkError(err)
if !equal(saved, []byte(message)) {
println("data verify error")
}
println(string(saved))
println()
println("Done.")
}
func equal(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func checkError(err error) {
+1 -1
View File
@@ -2,7 +2,7 @@ package main
import (
"log"
"machine/usb/joystick"
"machine/usb/hid/joystick"
"math"
"time"
)
+130
View File
@@ -0,0 +1,130 @@
// Example demonstrating I2C controller / target comms.
//
// To use this example, physically connect I2C0 and I2C1.
// I2C0 will be used as the controller and I2C1 used as
// the target.
//
// In this example, the target implements a simple memory
// map, with the controller able to read and write the
// memory.
package main
import (
"machine"
"time"
)
const (
targetAddress = 0x11
maxTxSize = 16
)
func main() {
// Delay to enable USB monitor time to attach
time.Sleep(3 * time.Second)
// Controller uses default I2C pins and controller
// mode is default
err := controller.Configure(machine.I2CConfig{})
if err != nil {
panic("failed to config I2C0 as controller")
}
// Target uses alternate pins and target mode is
// explicit
err = target.Configure(machine.I2CConfig{
Mode: machine.I2CModeTarget,
SCL: TARGET_SCL,
SDA: TARGET_SDA,
})
if err != nil {
panic("failed to config I2C1 as target")
}
// Put welcome message directly into target memory
copy(mem[0:], []byte("Hello World!"))
err = target.Listen(targetAddress)
if err != nil {
panic("failed to listen as I2C target")
}
// Start the target
go targetMain()
// Read welcome message from target over I2C
buf := make([]byte, 12)
err = controller.Tx(targetAddress, []byte{0}, buf)
if err != nil {
println("failed to read welcome message over I2C")
panic(err)
}
println("message from target:", string(buf))
// Write (1,2,3) to the target starting at memory address 3
println("writing (1,2,3) @ 0x3")
err = controller.Tx(targetAddress, []byte{3, 1, 2, 3}, nil)
if err != nil {
println("failed to write to I2C target")
panic(err)
}
time.Sleep(100 * time.Millisecond) // Wait for target to process write
println("mem:", mem[0], mem[1], mem[2], mem[3], mem[4], mem[5])
// Read memory address 4 from target, which should be the value 2
buf = make([]byte, 1)
err = controller.Tx(targetAddress, []byte{4}, buf[:1])
if err != nil {
println("failed to read from I2C target")
panic(err)
}
if buf[0] != 2 {
panic("read incorrect value from I2C target")
}
println("all done!")
for {
time.Sleep(1 * time.Second)
}
}
// -- target ---
var (
mem [256]byte
)
// targetMain implements the 'main loop' for an I2C target
func targetMain() {
buf := make([]byte, maxTxSize)
var ptr uint8
for {
evt, n, err := target.WaitForEvent(buf)
if err != nil {
panic(err)
}
switch evt {
case machine.I2CReceive:
if n > 0 {
ptr = buf[0]
}
for o := 1; o < n; o++ {
mem[ptr] = buf[o]
ptr++
}
case machine.I2CRequest:
target.Reply(mem[ptr:256])
case machine.I2CFinish:
// nothing to do
default:
}
}
}
@@ -0,0 +1,15 @@
//go:build feather_nrf52840
package main
import "machine"
const (
TARGET_SCL = machine.A5
TARGET_SDA = machine.A4
)
var (
controller = machine.I2C0
target = machine.I2C1
)
@@ -0,0 +1,15 @@
//go:build rp2040
package main
import "machine"
const (
TARGET_SCL = machine.GPIO25
TARGET_SDA = machine.GPIO24
)
var (
controller = machine.I2C1
target = machine.I2C0
)
+1 -1
View File
@@ -83,7 +83,7 @@ func in_ram() uintptr {
return device.AsmFull("MOV {}, PC", nil)
}
// go:noinline used here to prevent function being 'inlined' into main()
// 'go:noinline' used here to prevent function being 'inlined' into main()
// so it appears in objdump output. In normal use, go:inline is not
// required for functions running from flash (flash is the default).
//
+3 -4
View File
@@ -1,9 +1,9 @@
package main
import (
"fmt"
"machine"
"machine/usb/midi"
"machine/usb/adc/midi"
"time"
)
@@ -15,12 +15,11 @@ func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button := machine.BUTTON
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
m := midi.Port()
m.SetHandler(func(b []byte) {
led.Set(!led.Get())
fmt.Printf("% X\r\n", b)
m.Write(b)
})
+2 -2
View File
@@ -114,9 +114,9 @@ tinygo_swapTask:
pop {r4-r11, pc}
#else
pop {r4-r7}
.cfi_def_cfa_offset 5*9
.cfi_def_cfa_offset 5*4
pop {r0-r3}
.cfi_def_cfa_offset 1*9
.cfi_def_cfa_offset 1*4
mov r8, r0
mov r9, r1
mov r10, r2
+1
View File
@@ -9,4 +9,5 @@ type ADCConfig struct {
Reference uint32 // analog reference voltage (AREF) in millivolts
Resolution uint32 // number of bits for a single conversion (e.g., 8, 10, 12)
Samples uint32 // number of samples for a single conversion (e.g., 4, 8, 16, 32)
SampleTime uint32 // sample time, in microseconds (µs)
}
+80
View File
@@ -0,0 +1,80 @@
//go:build bluemicro840
package machine
const HasLowFrequencyCrystal = true
// GPIO Pins
const (
D006 = P0_06
D008 = P0_08
D015 = P0_15
D017 = P0_17
D020 = P0_20
D013 = P0_13
D024 = P0_24
D009 = P0_09
D010 = P0_10
D106 = P1_06
D031 = P0_31 // AIN7; P0.31 (AIN7) is used to read the voltage of the battery via ADC. It cant be used for any other function.
D012 = P0_12 // VCC 3.3V; P0.12 on VCC shuts off the power to VCC when you set it to high; This saves on battery immensely for LEDs of all kinds that eat power even when off
D030 = P0_30
D026 = P0_26
D029 = P0_29
D002 = P0_02
D113 = P1_13
D003 = P0_03
D028 = P0_28
D111 = P1_11
)
// Analog Pins
const (
AIN0 = P0_02
AIN1 = P0_03
AIN2 = P0_04 // Not Connected
AIN3 = P0_05 // Not Connected
AIN4 = P0_28
AIN5 = P0_29
AIN6 = P0_30
AIN7 = P0_31 // Battery
)
const (
LED1 Pin = P1_04 // Red LED
LED2 Pin = P1_10 // Blue LED
LED Pin = LED1
)
// UART0 pins (logical UART1) - Maps to same location as Pro Micro
const (
UART_RX_PIN = P0_08
UART_TX_PIN = P0_06
)
// I2C pins
const (
SDA_PIN = P0_15 // I2C0 external
SCL_PIN = P0_17 // I2C0 external
)
// SPI pins
const (
SPI0_SCK_PIN = P1_13 // SCK
SPI0_SDI_PIN = P0_03 // SDI
SPI0_SDO_PIN = P0_28 // SDO
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "bluemicro840"
usb_STRING_MANUFACTURER = "BlueMicro"
)
var (
usb_VID uint16 = 0x1d50
usb_PID uint16 = 0x6161
)
+117
View File
@@ -0,0 +1,117 @@
//go:build m5stick_c
// This file contains the pin mapping for the M5STACK M5Stick-C device.
// doc: https://docs.m5stack.com/en/core/m5stickc
package machine
const (
// HAT | HY2.0-4P
// |
// GND | GND
// 5V OUT | VOUT
// G26 | G32 SDA
// G36 | G33 SCL
// G0 |
// BAT |
// 3V3 |
// 5V IN |
IO0 = GPIO0 // CLK
IO1 = GPIO1 // U0TXD
IO2 = GPIO2
IO3 = GPIO3 // U0RXD
IO4 = GPIO4
IO5 = GPIO5 // TFT_CS LCD_SS SPI0_SS
IO6 = GPIO6
IO7 = GPIO7
IO8 = GPIO8
IO9 = GPIO9 // IR
IO10 = GPIO10 // LED
IO11 = GPIO11
IO12 = GPIO12
IO13 = GPIO13 // TFT_CLK LCD_SCK SPI0_SCK
IO14 = GPIO14
IO15 = GPIO15 // TFT_MOSI LCD_MOSI SPI0_MOSI
IO16 = GPIO16
IO17 = GPIO17
IO18 = GPIO18 // TFT_RST LCD_RST
IO19 = GPIO19
IO21 = GPIO21 // SDA0
IO22 = GPIO22 // SCL0
IO23 = GPIO23 // TFT_DC LCD_DC
IO25 = GPIO25 // - DAC1
IO26 = GPIO26 // HAT DAC2
IO27 = GPIO27
IO32 = GPIO32 // SDA1 / PIN 32 / RXD2
IO33 = GPIO33 // SCL1 / PIN 33 / TXD2
IO34 = GPIO34 // MIC_DATA
IO35 = GPIO35 // IRQ0 ADC1
IO36 = GPIO36 // HAT ADC2 LCD_MISO SPI0_MISO
IO37 = GPIO37 // BUTTON_A, BUTTON_HOME, BUTTON
IO38 = GPIO38
IO39 = GPIO39 // BUTTON_B, BUTTON_RST
)
const (
// Buttons
BUTTON_A = IO37
BUTTON_B = IO39
BUTTON_HOME = BUTTON_A
BUTTON_RST = BUTTON_B
BUTTON = BUTTON_A
// LED
IR = IO9
LED = IO10
)
// SPI pins
const (
SPI0_SCK_PIN = IO13
SPI0_SDO_PIN = IO15
SPI0_CS0_PIN = IO5
// LCD ()
LCD_SCK_PIN = SPI0_SCK_PIN
LCD_SDO_PIN = SPI0_SDO_PIN
LCD_SS_PIN = SPI0_CS0_PIN
LCD_DC_PIN = IO23
LCD_RST_PIN = IO18
)
// I2C pins
const (
// Internal I2C (AXP192 / BM8563 / MPU6886)
SDA0_PIN = IO21
SCL0_PIN = IO22
// External I2C (GROOVE PORT)
SDA1_PIN = IO32
SCL1_PIN = IO33
SDA_PIN = SDA1_PIN
SCL_PIN = SCL1_PIN
)
// ADC pins
const (
ADC1 Pin = IO35
ADC2 Pin = IO36
)
// DAC pins
const (
DAC1 Pin = IO25
DAC2 Pin = IO26
)
// UART pins
const (
// UART0 (CP2104)
UART0_TX_PIN = IO1
UART0_RX_PIN = IO3
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
@@ -1,4 +1,4 @@
//go:build pinetime_devkit0
//go:build pinetime
package machine
@@ -17,13 +17,10 @@ const (
LED = LED1
)
var DefaultUART = UART0
// UART pins for PineTime. Note that RX is set to NoPin as RXD is not listed in
// the PineTime schematic 1.0:
// http://files.pine64.org/doc/PineTime/PineTime%20Port%20Assignment%20rev1.0.pdf
// The PineTime doesn't have a UART output.
// Additionally, leaving the UART on results in a pretty big current drain.
const (
UART_TX_PIN Pin = 11 // TP29 (TXD)
UART_TX_PIN Pin = NoPin
UART_RX_PIN Pin = NoPin
)
+4
View File
@@ -96,3 +96,7 @@ var (
usb_VID uint16 = 0x2886
usb_PID uint16 = 0x802F
)
var (
DefaultUART = UART1
)
-79
View File
@@ -64,82 +64,3 @@ type BlockDevice interface {
// EraseBlockSize to map addresses to blocks.
EraseBlocks(start, len int64) error
}
// FlashBuffer implements the ReadWriteCloser interface using the BlockDevice interface.
type FlashBuffer struct {
b BlockDevice
// start is actual address
start uintptr
// offset is relative to start
offset uintptr
}
// OpenFlashBuffer opens a FlashBuffer.
func OpenFlashBuffer(b BlockDevice, address uintptr) *FlashBuffer {
return &FlashBuffer{b: b, start: address}
}
// Read data from a FlashBuffer.
func (fl *FlashBuffer) Read(p []byte) (n int, err error) {
n, err = fl.b.ReadAt(p, int64(fl.offset))
fl.offset += uintptr(n)
return
}
// Write data to a FlashBuffer.
func (fl *FlashBuffer) Write(p []byte) (n int, err error) {
// any new pages needed?
// NOTE probably will not work as expected if you try to write over page boundary
// of pages with different sizes.
pagesize := uintptr(fl.b.EraseBlockSize())
// calculate currentPageBlock relative to fl.start, meaning that
// block 0 -> fl.start
// block 1 -> fl.start + pagesize
// block 2 -> fl.start + pagesize*2
// ...
currentPageBlock := (fl.start + fl.offset - FlashDataStart()) + (pagesize-1)/pagesize
lastPageBlockNeeded := (fl.start + fl.offset + uintptr(len(p)) - FlashDataStart()) + (pagesize-1)/pagesize
// erase enough blocks to hold the data
if err := fl.b.EraseBlocks(int64(currentPageBlock), int64(lastPageBlockNeeded-currentPageBlock)); err != nil {
return 0, err
}
// write the data
for i := 0; i < len(p); i += int(pagesize) {
var last int = i + int(pagesize)
if i+int(pagesize) > len(p) {
last = len(p)
}
_, err := fl.b.WriteAt(p[i:last], int64(fl.offset))
if err != nil {
return 0, err
}
fl.offset += uintptr(len(p[i:last]))
}
return len(p), nil
}
// Close the FlashBuffer.
func (fl *FlashBuffer) Close() error {
return nil
}
// Seek implements io.Seeker interface, but with limitations.
// You can only seek relative to the start.
// Also, you cannot use seek before write operations, only read.
func (fl *FlashBuffer) Seek(offset int64, whence int) (int64, error) {
if whence != io.SeekStart {
panic("you can only Seek relative to Start")
}
fl.offset = uintptr(offset)
return offset, nil
}
+31
View File
@@ -23,6 +23,37 @@ var (
errI2CSignalStopTimeout = errors.New("I2C timeout on signal stop")
errI2CAckExpected = errors.New("I2C error: expected ACK not NACK")
errI2CBusError = errors.New("I2C bus error")
errI2COverflow = errors.New("I2C receive buffer overflow")
errI2COverread = errors.New("I2C transmit buffer overflow")
)
// I2CTargetEvent reflects events on the I2C bus
type I2CTargetEvent uint8
const (
// I2CReceive indicates target has received a message from the controller.
I2CReceive I2CTargetEvent = iota
// I2CRequest indicates the controller is expecting a message from the target.
I2CRequest
// I2CFinish indicates the controller has ended the transaction.
//
// I2C controllers can chain multiple receive/request messages without
// relinquishing the bus by doing 'restarts'. I2CFinish indicates the
// bus has been relinquished by an I2C 'stop'.
I2CFinish
)
// I2CMode determines if an I2C peripheral is in Controller or Target mode.
type I2CMode int
const (
// I2CModeController represents an I2C peripheral in controller mode.
I2CModeController I2CMode = iota
// I2CModeTarget represents an I2C peripheral in target mode.
I2CModeTarget
)
// WriteRegister transmits first the register and then the data to the
+3 -3
View File
@@ -1910,12 +1910,12 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
overflow := int64(len(p)) % f.WriteBlockSize()
if overflow == 0 {
return p
}
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
return append(p, padding...)
}
+2 -10
View File
@@ -139,8 +139,9 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
setup := usb.NewSetup(udd_ep_out_cache_buffer[0][:])
// Clear the Bank 0 ready flag on Control OUT
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
ok := false
if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD {
@@ -344,15 +345,6 @@ func sendUSBPacket(ep uint32, data []byte, maxsize uint16) {
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
var b [cdcLineInfoSize]byte
// address
usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0]))))
// set byte count to zero
usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
// set ready for next data
setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY)
// Wait until OUT transfer is ready.
timeout := 300000
for (getEPSTATUS(0) & sam.USB_DEVICE_EPSTATUS_BK0RDY) == 0 {
+8 -4
View File
@@ -46,10 +46,14 @@ func init() {
// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
func (p Pin) PortMaskSet() (*uint32, uint32) {
// Note: using PORT_IOBUS for faster pin accesses.
// The regular PORT registers appear to take around 4 clock cycles to store,
// which is longer than the ws2812 driver expects. The IOBUS is is fast
// enough to avoid this issue.
if p < 32 {
return &sam.PORT.OUTSET0.Reg, 1 << uint8(p)
return &sam.PORT_IOBUS.OUTSET0.Reg, 1 << uint8(p)
} else {
return &sam.PORT.OUTSET1.Reg, 1 << uint8(p-32)
return &sam.PORT_IOBUS.OUTSET1.Reg, 1 << uint8(p-32)
}
}
@@ -57,9 +61,9 @@ func (p Pin) PortMaskSet() (*uint32, uint32) {
// implement bit-banged drivers.
func (p Pin) PortMaskClear() (*uint32, uint32) {
if p < 32 {
return &sam.PORT.OUTCLR0.Reg, 1 << uint8(p)
return &sam.PORT_IOBUS.OUTCLR0.Reg, 1 << uint8(p)
} else {
return &sam.PORT.OUTCLR1.Reg, 1 << uint8(p-32)
return &sam.PORT_IOBUS.OUTCLR1.Reg, 1 << uint8(p-32)
}
}
+79 -46
View File
@@ -749,36 +749,10 @@ func (a ADC) Configure(config ADCConfig) {
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_CTRLB) {
} // wait for sync
adc.CTRLA.SetBits(sam.ADC_CTRLA_PRESCALER_DIV32 << sam.ADC_CTRLA_PRESCALER_Pos)
var resolution uint32
switch config.Resolution {
case 8:
resolution = sam.ADC_CTRLB_RESSEL_8BIT
case 10:
resolution = sam.ADC_CTRLB_RESSEL_10BIT
case 12:
resolution = sam.ADC_CTRLB_RESSEL_12BIT
case 16:
resolution = sam.ADC_CTRLB_RESSEL_16BIT
default:
resolution = sam.ADC_CTRLB_RESSEL_12BIT
}
adc.CTRLB.SetBits(uint16(resolution << sam.ADC_CTRLB_RESSEL_Pos))
adc.SAMPCTRL.Set(5) // sampling Time Length
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_SAMPCTRL) {
} // wait for sync
// No Negative input (Internal Ground)
adc.INPUTCTRL.Set(sam.ADC_INPUTCTRL_MUXNEG_GND << sam.ADC_INPUTCTRL_MUXNEG_Pos)
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_INPUTCTRL) {
} // wait for sync
// Averaging (see datasheet table in AVGCTRL register description)
var resolution uint32 = sam.ADC_CTRLB_RESSEL_16BIT
var samples uint32
switch config.Samples {
case 1:
samples = sam.ADC_AVGCTRL_SAMPLENUM_1
case 2:
samples = sam.ADC_AVGCTRL_SAMPLENUM_2
case 4:
@@ -800,11 +774,39 @@ func (a ADC) Configure(config ADCConfig) {
case 1024:
samples = sam.ADC_AVGCTRL_SAMPLENUM_1024
default: // 1 sample only (no oversampling nor averaging), adjusting result by 0
// Resolutions less than 16 bits only make sense when sampling only
// once. Resulting ADC values become erratic when using both
// multi-sampling and less than 16 bits of resolution.
samples = sam.ADC_AVGCTRL_SAMPLENUM_1
switch config.Resolution {
case 8:
resolution = sam.ADC_CTRLB_RESSEL_8BIT
case 10:
resolution = sam.ADC_CTRLB_RESSEL_10BIT
case 12:
resolution = sam.ADC_CTRLB_RESSEL_12BIT
case 16:
resolution = sam.ADC_CTRLB_RESSEL_16BIT
default:
resolution = sam.ADC_CTRLB_RESSEL_12BIT
}
}
adc.AVGCTRL.Set(uint8(samples<<sam.ADC_AVGCTRL_SAMPLENUM_Pos) |
(0 << sam.ADC_AVGCTRL_ADJRES_Pos))
adc.CTRLA.SetBits(sam.ADC_CTRLA_PRESCALER_DIV32 << sam.ADC_CTRLA_PRESCALER_Pos)
adc.CTRLB.SetBits(uint16(resolution << sam.ADC_CTRLB_RESSEL_Pos))
adc.SAMPCTRL.Set(5) // sampling Time Length
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_SAMPCTRL) {
} // wait for sync
// No Negative input (Internal Ground)
adc.INPUTCTRL.Set(sam.ADC_INPUTCTRL_MUXNEG_GND << sam.ADC_INPUTCTRL_MUXNEG_Pos)
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_INPUTCTRL) {
} // wait for sync
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_AVGCTRL) {
} // wait for sync
for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_REFCTRL) {
@@ -871,10 +873,24 @@ func (a ADC) Get() uint16 {
val = val << 8
case sam.ADC_CTRLB_RESSEL_10BIT:
val = val << 6
case sam.ADC_CTRLB_RESSEL_16BIT:
val = val << 4
case sam.ADC_CTRLB_RESSEL_12BIT:
val = val << 4
case sam.ADC_CTRLB_RESSEL_16BIT:
// Adjust for multiple samples. This is only configured when the
// resolution is 16 bits.
switch (bus.AVGCTRL.Get() & sam.ADC_AVGCTRL_SAMPLENUM_Msk) >> sam.ADC_AVGCTRL_SAMPLENUM_Pos {
case sam.ADC_AVGCTRL_SAMPLENUM_1:
val <<= 4
case sam.ADC_AVGCTRL_SAMPLENUM_2:
val <<= 3
case sam.ADC_AVGCTRL_SAMPLENUM_4:
val <<= 2
case sam.ADC_AVGCTRL_SAMPLENUM_8:
val <<= 1
default:
// These values are all shifted by the hardware so they fit exactly
// in a 16-bit integer, so they don't need to be shifted here.
}
}
return val
}
@@ -1490,22 +1506,39 @@ func (spi SPI) Configure(config SPIConfig) error {
spi.Bus.CTRLA.ClearBits(sam.SERCOM_SPIM_CTRLA_CPOL)
}
// set clock
freqRef := uint32(0)
if config.Frequency > SERCOM_FREQ_REF/2 {
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK0)
freqRef = uint32(SERCOM_FREQ_REF_GCLK0)
} else {
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1)
freqRef = uint32(SERCOM_FREQ_REF)
}
// Set the clock frequency.
// There are two clocks we can use GCLK0 (120MHz) and GCLK1 (48MHz).
// We can use any even divisor for these clock, which means:
// - for GCLK0 we can make 60MHz, 30MHz, 20MHz, 15MHz, 12MHz, 10MHz, etc
// - for GCLK1 we can make 24MHz, 12MHz, 8MHz, 6MHz, 4.8MHz, 4MHz, etc
// This means that by trying both clocks, we can have a wider selection of
// available SPI clock frequencies.
// Set synch speed for SPI
baudRate := freqRef / (2 * config.Frequency)
if baudRate > 0 {
baudRate--
// Calculate the baudrate if we would use GCLK1 (48MHz), and the resulting
// frequency. The baud rate is rounded up, so that the resulting frequency
// is rounded down from the maximum value (meaning it will always be smaller
// than or equal to config.Frequency).
baudRateGCLK1 := (SERCOM_FREQ_REF/2 + config.Frequency - 1) / config.Frequency
freqGCLK1 := SERCOM_FREQ_REF / 2 / baudRateGCLK1
// Same for GCLK0 (120MHz).
baudRateGCLK0 := (SERCOM_FREQ_REF_GCLK0/2 + config.Frequency - 1) / config.Frequency
freqGCLK0 := SERCOM_FREQ_REF_GCLK0 / 2 / baudRateGCLK0
// Pick the clock source that is the closest to the maximum baud rate.
// Note: there may be reasons to prefer the lower frequency clock (like
// power consumption). If that's the case, we might want to always use the
// 48MHz clock at low frequencies (below 4MHz or so).
if freqGCLK0 > freqGCLK1 && uint32(uint8(baudRateGCLK0-1))+1 == baudRateGCLK0 {
// Pick this 120MHz clock if it results in a better frequency after
// division, and the baudRate value fits in the BAUD register.
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK0)
spi.Bus.BAUD.Set(uint8(baudRateGCLK0 - 1))
} else {
// Use the 48MHz clock in other cases.
setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1)
spi.Bus.BAUD.Set(uint8(baudRateGCLK1 - 1))
}
spi.Bus.BAUD.Set(uint8(baudRate))
// Enable SPI port.
spi.Bus.CTRLA.SetBits(sam.SERCOM_SPIM_CTRLA_ENABLE)
@@ -2206,12 +2239,12 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
overflow := int64(len(p)) % f.WriteBlockSize()
if overflow == 0 {
return p
}
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
return append(p, padding...)
}
+52
View File
@@ -0,0 +1,52 @@
//go:build attiny1616
package machine
import (
"device/avr"
)
const (
portA Pin = iota * 8
portB
portC
)
const (
PA0 = portA + 0
PA1 = portA + 1
PA2 = portA + 2
PA3 = portA + 3
PA4 = portA + 4
PA5 = portA + 5
PA6 = portA + 6
PA7 = portA + 7
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
)
// getPortMask returns the PORT peripheral and mask for the pin.
func (p Pin) getPortMask() (*avr.PORT_Type, uint8) {
switch {
case p >= PA0 && p <= PA7: // port A
return avr.PORTA, 1 << uint8(p-portA)
case p >= PB0 && p <= PB7: // port B
return avr.PORTB, 1 << uint8(p-portB)
default: // port C
return avr.PORTC, 1 << uint8(p-portC)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build avr
//go:build avr && !avrtiny
package machine
+62
View File
@@ -0,0 +1,62 @@
//go:build avrtiny
package machine
import (
"device/avr"
"runtime/volatile"
"unsafe"
)
const deviceName = avr.DEVICE
const (
PinInput PinMode = iota
PinInputPullup
PinOutput
)
// Configure sets the pin to input or output.
func (p Pin) Configure(config PinConfig) {
port, mask := p.getPortMask()
if config.Mode == PinOutput {
// set output bit
port.DIRSET.Set(mask)
// Note: the output state (high or low) is as it was before.
} else {
// Configure the pin as an input.
// First set up the configuration that will be used when it is an input.
pinctrl := uint8(0)
if config.Mode == PinInputPullup {
pinctrl |= avr.PORT_PIN0CTRL_PULLUPEN
}
// Find the PINxCTRL register for this pin.
ctrlAddress := (*volatile.Register8)(unsafe.Add(unsafe.Pointer(&port.PIN0CTRL), p%8))
ctrlAddress.Set(pinctrl)
// Configure the pin as input (if it wasn't an input pin before).
port.DIRCLR.Set(mask)
}
}
// Get returns the current value of a GPIO pin when the pin is configured as an
// input or as an output.
func (p Pin) Get() bool {
port, mask := p.getPortMask()
// As noted above, the PINx register is always two registers below the PORTx
// register, so we can find it simply by subtracting two from the PORTx
// register address.
return (port.IN.Get() & mask) > 0
}
// Set changes the value of the GPIO pin. The pin must be configured as output.
func (p Pin) Set(high bool) {
port, mask := p.getPortMask()
if high {
port.OUTSET.Set(mask)
} else {
port.OUTCLR.Set(mask)
}
}
+10
View File
@@ -0,0 +1,10 @@
//go:build cortexm
package machine
import "device/arm"
// CPUReset performs a hard system reset.
func CPUReset() {
arm.SystemReset()
}
+6 -6
View File
@@ -38,27 +38,27 @@ func (p Pin) Set(value bool) {
// do nothing
}
var Display = FramebufDisplay{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))}
var Display = DisplayMode3{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))}
type FramebufDisplay struct {
type DisplayMode3 struct {
port *[160][240]volatile.Register16
}
func (d FramebufDisplay) Configure() {
func (d *DisplayMode3) Configure() {
// Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) and Enable BG2
gba.DISP.DISPCNT.Set(gba.DISPCNT_BGMODE_3<<gba.DISPCNT_BGMODE_Pos |
gba.DISPCNT_SCREENDISPLAY_BG2_ENABLE<<gba.DISPCNT_SCREENDISPLAY_BG2_Pos)
}
func (d FramebufDisplay) Size() (x, y int16) {
func (d *DisplayMode3) Size() (x, y int16) {
return 240, 160
}
func (d FramebufDisplay) SetPixel(x, y int16, c color.RGBA) {
func (d *DisplayMode3) SetPixel(x, y int16, c color.RGBA) {
d.port[y][x].Set((uint16(c.R) >> 3) | ((uint16(c.G) >> 3) << 5) | ((uint16(c.B) >> 3) << 10))
}
func (d FramebufDisplay) Display() error {
func (d *DisplayMode3) Display() error {
// Nothing to do here.
return nil
}
+18 -99
View File
@@ -201,28 +201,18 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
}
}
// I2C on the NRF.
type I2C struct {
Bus nrf.TWI_Type
}
// There are 2 I2C interfaces on the NRF.
var (
I2C0 = (*I2C)(unsafe.Pointer(nrf.TWI0))
I2C1 = (*I2C)(unsafe.Pointer(nrf.TWI1))
)
// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
Frequency uint32
SCL Pin
SDA Pin
Mode I2CMode
}
// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Disabled)
i2c.disable()
// Default I2C bus speed is 100 kHz.
if config.Frequency == 0 {
@@ -249,71 +239,25 @@ func (i2c *I2C) Configure(config I2CConfig) error {
(nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) |
(nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos))
if config.Frequency >= 400*KHz {
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400)
} else {
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
}
i2c.setPins(config.SCL, config.SDA)
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled)
i2c.mode = config.Mode
if i2c.mode == I2CModeController {
if config.Frequency >= 400*KHz {
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400)
} else {
i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100)
}
i2c.enableAsController()
} else {
i2c.enableAsTarget()
}
return nil
}
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
// Tricky stop condition.
// After reads, the stop condition is generated implicitly with a shortcut.
// After writes not followed by reads and in the case of errors, stop must be generated explicitly.
i2c.Bus.ADDRESS.Set(uint32(addr))
if len(w) != 0 {
i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing
for _, b := range w {
if err = i2c.writeByte(b); err != nil {
i2c.signalStop()
return
}
}
}
if len(r) != 0 {
// To trigger suspend task when a byte is received
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND)
i2c.Bus.TASKS_STARTRX.Set(1) // re-start transmission for reading
for i := range r { // read each char
if i+1 == len(r) {
// To trigger stop task when last byte is received, set before resume task.
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_STOP)
}
if i > 0 {
i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading
}
if r[i], err = i2c.readByte(); err != nil {
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
i2c.signalStop()
return
}
}
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
}
// Stop explicitly when no reads were executed, stoping unconditionally would be a mistake.
// It may execute after I2C peripheral has already been stopped by the shortcut in the read block,
// so stop task will trigger first thing in a subsequent transaction, hanging it.
if len(r) == 0 {
i2c.signalStop()
}
return
}
// signalStop sends a stop signal to the I2C peripheral and waits for confirmation.
func (i2c *I2C) signalStop() {
i2c.Bus.TASKS_STOP.Set(1)
@@ -322,31 +266,6 @@ func (i2c *I2C) signalStop() {
i2c.Bus.EVENTS_STOPPED.Set(0)
}
// writeByte writes a single byte to the I2C bus and waits for confirmation.
func (i2c *I2C) writeByte(data byte) error {
i2c.Bus.TXD.Set(uint32(data))
for i2c.Bus.EVENTS_TXDSENT.Get() == 0 {
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
i2c.Bus.EVENTS_ERROR.Set(0)
return errI2CBusError
}
}
i2c.Bus.EVENTS_TXDSENT.Set(0)
return nil
}
// readByte reads a single byte from the I2C bus when it is ready.
func (i2c *I2C) readByte() (byte, error) {
for i2c.Bus.EVENTS_RXDREADY.Get() == 0 {
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
i2c.Bus.EVENTS_ERROR.Set(0)
return 0, errI2CBusError
}
}
i2c.Bus.EVENTS_RXDREADY.Set(0)
return byte(i2c.Bus.RXD.Get()), nil
}
var rngStarted = false
// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise.
@@ -477,12 +396,12 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
overflow := int64(len(p)) % f.WriteBlockSize()
if overflow == 0 {
return p
}
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
return append(p, padding...)
}
+172 -419
View File
@@ -1,463 +1,216 @@
//go:build nrf52 || nrf52840 || nrf52833
//go:build nrf52840 || nrf52833
package machine
import (
"device/nrf"
"runtime/volatile"
"unsafe"
)
func CPUFrequency() uint32 {
return 64000000
// I2C on the NRF528xx.
type I2C struct {
Bus *nrf.TWIM_Type // Called Bus to align with Bus field in nrf51
BusT *nrf.TWIS_Type
mode I2CMode
}
// InitADC initializes the registers needed for ADC.
func InitADC() {
return // no specific setup on nrf52 machine.
}
// Configure configures an ADC pin to be able to read analog data.
func (a ADC) Configure(ADCConfig) {
return // no pin specific setup on nrf52 machine.
}
// Get returns the current value of a ADC pin in the range 0..0xffff.
func (a ADC) Get() uint16 {
var pwmPin uint32
var rawValue volatile.Register16
switch a.Pin {
case 2:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0
case 3:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1
case 4:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2
case 5:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3
case 28:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4
case 29:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5
case 30:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6
case 31:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7
default:
return 0
}
nrf.SAADC.RESOLUTION.Set(nrf.SAADC_RESOLUTION_VAL_12bit)
// Enable ADC.
nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos)
for i := 0; i < 8; i++ {
nrf.SAADC.CH[i].PSELN.Set(nrf.SAADC_CH_PSELP_PSELP_NC)
nrf.SAADC.CH[i].PSELP.Set(nrf.SAADC_CH_PSELP_PSELP_NC)
}
// Configure ADC.
nrf.SAADC.CH[0].CONFIG.Set(((nrf.SAADC_CH_CONFIG_RESP_Bypass << nrf.SAADC_CH_CONFIG_RESP_Pos) & nrf.SAADC_CH_CONFIG_RESP_Msk) |
((nrf.SAADC_CH_CONFIG_RESP_Bypass << nrf.SAADC_CH_CONFIG_RESN_Pos) & nrf.SAADC_CH_CONFIG_RESN_Msk) |
((nrf.SAADC_CH_CONFIG_GAIN_Gain1_5 << nrf.SAADC_CH_CONFIG_GAIN_Pos) & nrf.SAADC_CH_CONFIG_GAIN_Msk) |
((nrf.SAADC_CH_CONFIG_REFSEL_Internal << nrf.SAADC_CH_CONFIG_REFSEL_Pos) & nrf.SAADC_CH_CONFIG_REFSEL_Msk) |
((nrf.SAADC_CH_CONFIG_TACQ_3us << nrf.SAADC_CH_CONFIG_TACQ_Pos) & nrf.SAADC_CH_CONFIG_TACQ_Msk) |
((nrf.SAADC_CH_CONFIG_MODE_SE << nrf.SAADC_CH_CONFIG_MODE_Pos) & nrf.SAADC_CH_CONFIG_MODE_Msk))
// Set pin to read.
nrf.SAADC.CH[0].PSELN.Set(pwmPin)
nrf.SAADC.CH[0].PSELP.Set(pwmPin)
// Destination for sample result.
nrf.SAADC.RESULT.PTR.Set(uint32(uintptr(unsafe.Pointer(&rawValue))))
nrf.SAADC.RESULT.MAXCNT.Set(1) // One sample
// Start tasks.
nrf.SAADC.TASKS_START.Set(1)
for nrf.SAADC.EVENTS_STARTED.Get() == 0 {
}
nrf.SAADC.EVENTS_STARTED.Set(0x00)
// Start the sample task.
nrf.SAADC.TASKS_SAMPLE.Set(1)
// Wait until the sample task is done.
for nrf.SAADC.EVENTS_END.Get() == 0 {
}
nrf.SAADC.EVENTS_END.Set(0x00)
// Stop the ADC
nrf.SAADC.TASKS_STOP.Set(1)
for nrf.SAADC.EVENTS_STOPPED.Get() == 0 {
}
nrf.SAADC.EVENTS_STOPPED.Set(0)
// Disable the ADC.
nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Disabled << nrf.SAADC_ENABLE_ENABLE_Pos)
value := int16(rawValue.Get())
if value < 0 {
value = 0
}
// Return 16-bit result from 12-bit value.
return uint16(value << 4)
}
// SPI on the NRF.
type SPI struct {
Bus *nrf.SPIM_Type
buf *[1]byte // 1-byte buffer for the Transfer method
}
// There are 3 SPI interfaces on the NRF528xx.
// There are 2 I2C interfaces on the NRF.
var (
SPI0 = SPI{Bus: nrf.SPIM0, buf: new([1]byte)}
SPI1 = SPI{Bus: nrf.SPIM1, buf: new([1]byte)}
SPI2 = SPI{Bus: nrf.SPIM2, buf: new([1]byte)}
I2C0 = &I2C{Bus: nrf.TWIM0, BusT: nrf.TWIS0}
I2C1 = &I2C{Bus: nrf.TWIM1, BusT: nrf.TWIS1}
)
// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
Frequency uint32
SCK Pin
SDO Pin
SDI Pin
LSBFirst bool
Mode uint8
func (i2c *I2C) enableAsController() {
i2c.Bus.ENABLE.Set(nrf.TWIM_ENABLE_ENABLE_Enabled)
}
// Configure is intended to setup the SPI interface.
func (spi SPI) Configure(config SPIConfig) {
// Disable bus to configure it
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled)
// Pick a default frequency.
if config.Frequency == 0 {
config.Frequency = 4000000 // 4MHz
}
// set frequency
var freq uint32
switch {
case config.Frequency >= 8000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M8
case config.Frequency >= 4000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M4
case config.Frequency >= 2000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M2
case config.Frequency >= 1000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M1
case config.Frequency >= 500000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K500
case config.Frequency >= 250000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K250
default: // below 250kHz, default to the lowest speed available
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K125
}
spi.Bus.FREQUENCY.Set(freq)
var conf uint32
// set bit transfer order
if config.LSBFirst {
conf = (nrf.SPIM_CONFIG_ORDER_LsbFirst << nrf.SPIM_CONFIG_ORDER_Pos)
}
// set mode
switch config.Mode {
case 0:
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
case 1:
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
case 2:
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
case 3:
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
default: // to mode
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
}
spi.Bus.CONFIG.Set(conf)
// set pins
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
config.SCK = SPI0_SCK_PIN
config.SDO = SPI0_SDO_PIN
config.SDI = SPI0_SDI_PIN
}
spi.Bus.PSEL.SCK.Set(uint32(config.SCK))
spi.Bus.PSEL.MOSI.Set(uint32(config.SDO))
spi.Bus.PSEL.MISO.Set(uint32(config.SDI))
// Re-enable bus now that it is configured.
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled)
func (i2c *I2C) enableAsTarget() {
i2c.BusT.ENABLE.Set(nrf.TWIS_ENABLE_ENABLE_Enabled)
}
// Transfer writes/reads a single byte using the SPI interface.
func (spi SPI) Transfer(w byte) (byte, error) {
buf := spi.buf[:]
buf[0] = w
err := spi.Tx(buf[:], buf[:])
return buf[0], err
func (i2c *I2C) disable() {
i2c.Bus.ENABLE.Set(0)
}
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous
// write/read interface, there must always be the same number of bytes written
// as bytes read. Therefore, if the number of bytes don't match it will be
// padded until they fit: if len(w) > len(r) the extra bytes received will be
// dropped and if len(w) < len(r) extra 0 bytes will be sent.
func (spi SPI) Tx(w, r []byte) error {
// Unfortunately the hardware (on the nrf52832) only supports up to 255
// bytes in the buffers, so if either w or r is longer than that the
// transfer needs to be broken up in pieces.
// The nrf52840 supports far larger buffers however, which isn't yet
// supported.
for len(r) != 0 || len(w) != 0 {
// Prepare the SPI transfer: set the DMA pointers and lengths.
if len(r) != 0 {
spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
n := uint32(len(r))
if n > 255 {
n = 255
// Tx does a single I2C transaction at the specified address (when in controller mode).
//
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
i2c.Bus.ADDRESS.Set(uint32(addr))
i2c.Bus.EVENTS_STOPPED.Set(0)
i2c.Bus.EVENTS_ERROR.Set(0)
i2c.Bus.EVENTS_RXSTARTED.Set(0)
i2c.Bus.EVENTS_TXSTARTED.Set(0)
i2c.Bus.EVENTS_LASTRX.Set(0)
i2c.Bus.EVENTS_LASTTX.Set(0)
i2c.Bus.EVENTS_SUSPENDED.Set(0)
// Configure for a single shot to perform both write and read (as applicable)
if len(w) != 0 {
i2c.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
i2c.Bus.TXD.MAXCNT.Set(uint32(len(w)))
// If no read, immediately signal stop after TX
if len(r) == 0 {
i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STOP)
}
}
if len(r) != 0 {
i2c.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
i2c.Bus.RXD.MAXCNT.Set(uint32(len(r)))
// Auto-start Rx after Tx and Stop after Rx
i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STARTRX | nrf.TWIM_SHORTS_LASTRX_STOP)
}
// Fire the transaction
i2c.Bus.TASKS_RESUME.Set(1)
if len(w) != 0 {
i2c.Bus.TASKS_STARTTX.Set(1)
} else if len(r) != 0 {
i2c.Bus.TASKS_STARTRX.Set(1)
}
// Wait until transaction stopped to ensure buffers fully processed
for i2c.Bus.EVENTS_STOPPED.Get() == 0 {
// Allow scheduler to run
gosched()
// Handle errors by ensuring STOP sent on bus
if i2c.Bus.EVENTS_ERROR.Get() != 0 {
if i2c.Bus.EVENTS_STOPPED.Get() == 0 {
// STOP cannot be sent during SUSPEND
i2c.Bus.TASKS_RESUME.Set(1)
i2c.Bus.TASKS_STOP.Set(1)
}
spi.Bus.RXD.MAXCNT.Set(n)
r = r[n:]
err = twiCError(i2c.Bus.ERRORSRC.Get())
}
if len(w) != 0 {
spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
n := uint32(len(w))
if n > 255 {
n = 255
}
spi.Bus.TXD.MAXCNT.Set(n)
w = w[n:]
}
// Do the transfer.
// Note: this can be improved by not waiting until the transfer is
// finished if the transfer is send-only (a common case).
spi.Bus.TASKS_START.Set(1)
for spi.Bus.EVENTS_END.Get() == 0 {
}
spi.Bus.EVENTS_END.Set(0)
}
return
}
// Listen starts listening for I2C requests sent to specified address
//
// addr is the address to listen to
func (i2c *I2C) Listen(addr uint8) error {
i2c.BusT.ADDRESS[0].Set(uint32(addr))
i2c.BusT.CONFIG.Set(nrf.TWIS_CONFIG_ADDRESS0_Enabled)
i2c.BusT.EVENTS_STOPPED.Set(0)
i2c.BusT.EVENTS_ERROR.Set(0)
i2c.BusT.EVENTS_RXSTARTED.Set(0)
i2c.BusT.EVENTS_TXSTARTED.Set(0)
i2c.BusT.EVENTS_WRITE.Set(0)
i2c.BusT.EVENTS_READ.Set(0)
return nil
}
// PWM is one PWM peripheral, which consists of a counter and multiple output
// channels (that can be connected to actual pins). You can set the frequency
// using SetPeriod, but only for all the channels in this PWM peripheral at
// once.
type PWM struct {
PWM *nrf.PWM_Type
// WaitForEvent blocks the current go-routine until an I2C event is received (when in Target mode).
//
// The passed buffer will be populated for receive events, with the number of bytes
// received returned in count. For other event types, buf is not modified and a count
// of zero is returned.
//
// For request events, the caller MUST call `Reply` to avoid hanging the i2c bus indefinitely.
func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
i2c.BusT.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf)))
channelValues [4]volatile.Register16
}
i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger)
// Configure enables and configures this PWM.
// On the nRF52 series, the maximum period is around 0.26s.
func (pwm *PWM) Configure(config PWMConfig) error {
// Enable the peripheral.
pwm.PWM.ENABLE.Set(nrf.PWM_ENABLE_ENABLE_Enabled << nrf.PWM_ENABLE_ENABLE_Pos)
i2c.Bus.TASKS_RESUME.Set(1)
// Use up counting only. TODO: allow configuring as up-and-down.
pwm.PWM.MODE.Set(nrf.PWM_MODE_UPDOWN_Up << nrf.PWM_MODE_UPDOWN_Pos)
for i2c.BusT.EVENTS_STOPPED.Get() == 0 &&
i2c.BusT.EVENTS_READ.Get() == 0 {
gosched()
// Indicate there are four channels that each have a different value.
pwm.PWM.DECODER.Set(nrf.PWM_DECODER_LOAD_Individual<<nrf.PWM_DECODER_LOAD_Pos | nrf.PWM_DECODER_MODE_RefreshCount<<nrf.PWM_DECODER_MODE_Pos)
err := pwm.setPeriod(config.Period, true)
if err != nil {
return err
if i2c.BusT.EVENTS_ERROR.Get() != 0 {
i2c.BusT.EVENTS_ERROR.Set(0)
return I2CReceive, 0, twisError(i2c.BusT.ERRORSRC.Get())
}
}
// Set the EasyDMA buffer, which has 4 values (one for each channel).
pwm.PWM.SEQ[0].PTR.Set(uint32(uintptr(unsafe.Pointer(&pwm.channelValues[0]))))
pwm.PWM.SEQ[0].CNT.Set(4)
count = 0
evt = I2CFinish
err = nil
// SEQ[0] is not yet started, it will be started on the first
// PWMChannel.Set() call.
if i2c.BusT.EVENTS_WRITE.Get() != 0 {
i2c.BusT.EVENTS_WRITE.Set(0)
// Data was sent to this target. We've waited for
// READ or STOPPED event, so transmission should be
// complete.
count = int(i2c.BusT.RXD.AMOUNT.Get())
evt = I2CReceive
} else if i2c.BusT.EVENTS_READ.Get() != 0 {
i2c.BusT.EVENTS_READ.Set(0)
// Data is requested from this target, hw will stretch
// the controller's clock until there is a reply to
// send
evt = I2CRequest
} else if i2c.BusT.EVENTS_STOPPED.Get() != 0 {
i2c.BusT.EVENTS_STOPPED.Set(0)
evt = I2CFinish
}
return
}
// Reply supplies the response data the controller.
func (i2c *I2C) Reply(buf []byte) error {
i2c.BusT.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf)))
i2c.BusT.EVENTS_STOPPED.Set(0)
// Trigger Tx
i2c.BusT.TASKS_PREPARETX.Set(nrf.TWIS_TASKS_PREPARETX_TASKS_PREPARETX_Trigger)
// Block, waiting for Tx to complete
for i2c.BusT.EVENTS_STOPPED.Get() == 0 {
gosched()
if i2c.BusT.EVENTS_ERROR.Get() != 0 {
return twisError(i2c.BusT.ERRORSRC.Get())
}
}
i2c.BusT.EVENTS_STOPPED.Set(0)
return nil
}
// SetPeriod updates the period of this PWM peripheral.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the PWM peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (pwm *PWM) SetPeriod(period uint64) error {
return pwm.setPeriod(period, false)
}
func (pwm *PWM) setPeriod(period uint64, updatePrescaler bool) error {
const maxTop = 0x7fff // 15 bits counter
// The top value is the number of PWM ticks a PWM period takes. It is
// initially picked assuming an unlimited COUNTERTOP and no PWM prescaler.
var top uint64
if period == 0 {
// The period is 0, which means "pick something reasonable for LEDs".
top = maxTop
} else {
// The formula below calculates the following formula, optimized:
// period * (16e6 / 1e9)
// The max frequency (16e6 or 16MHz) is set by the hardware.
top = period * 2 / 125
// twiCError converts an I2C controller error to Go
func twiCError(val uint32) error {
if val == 0 {
return nil
} else if val&nrf.TWIM_ERRORSRC_OVERRUN_Msk == nrf.TWIM_ERRORSRC_OVERRUN {
return errI2CBusError
} else if val&nrf.TWIM_ERRORSRC_ANACK_Msk == nrf.TWIM_ERRORSRC_ANACK {
return errI2CAckExpected
} else if val&nrf.TWIM_ERRORSRC_DNACK_Msk == nrf.TWIM_ERRORSRC_DNACK {
return errI2CAckExpected
}
// The ideal PWM period may be larger than would fit in the PWM counter,
// which is only 15 bits (see maxTop). Therefore, try to make the PWM clock
// speed lower with a prescaler to make the top value fit the COUNTERTOP.
if updatePrescaler {
// This function was called during Configure().
switch {
case top <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_1)
case top/2 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_2)
top /= 2
case top/4 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_4)
top /= 4
case top/8 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_8)
top /= 8
case top/16 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_16)
top /= 16
case top/32 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_32)
top /= 32
case top/64 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_64)
top /= 64
case top/128 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_128)
top /= 128
default:
return ErrPWMPeriodTooLong
}
} else {
// Do not update the prescaler, but use the already-configured
// prescaler. This is the normal SetPeriod case, where the prescaler
// must not be changed.
prescaler := pwm.PWM.PRESCALER.Get()
switch prescaler {
case nrf.PWM_PRESCALER_PRESCALER_DIV_1:
top /= 1
case nrf.PWM_PRESCALER_PRESCALER_DIV_2:
top /= 2
case nrf.PWM_PRESCALER_PRESCALER_DIV_4:
top /= 4
case nrf.PWM_PRESCALER_PRESCALER_DIV_8:
top /= 8
case nrf.PWM_PRESCALER_PRESCALER_DIV_16:
top /= 16
case nrf.PWM_PRESCALER_PRESCALER_DIV_32:
top /= 32
case nrf.PWM_PRESCALER_PRESCALER_DIV_64:
top /= 64
case nrf.PWM_PRESCALER_PRESCALER_DIV_128:
top /= 128
}
if top > maxTop {
return ErrPWMPeriodTooLong
}
}
pwm.PWM.COUNTERTOP.Set(uint32(top))
// Apparently this is needed to apply the new COUNTERTOP.
pwm.PWM.TASKS_SEQSTART[0].Set(1)
return nil
return errI2CBusError
}
// Top returns the current counter top, for use in duty cycle calculation. It
// will only change with a call to Configure or SetPeriod, otherwise it is
// constant.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to
// pwm.Set (see pwm.Set for more information).
func (pwm *PWM) Top() uint32 {
return pwm.PWM.COUNTERTOP.Get()
}
// Channel returns a PWM channel for the given pin.
func (pwm *PWM) Channel(pin Pin) (uint8, error) {
config := uint32(pin)
for ch := uint8(0); ch < 4; ch++ {
channelConfig := pwm.PWM.PSEL.OUT[ch].Get()
if channelConfig == 0xffffffff {
// Unused channel. Configure it.
pwm.PWM.PSEL.OUT[ch].Set(config)
// Configure the pin (required by the reference manual).
pin.Configure(PinConfig{Mode: PinOutput})
// Set channel to zero and non-inverting.
pwm.channelValues[ch].Set(0x8000)
return ch, nil
} else if channelConfig == config {
// This channel is already configured for this pin.
return ch, nil
}
// twisError converts an I2C target error to Go
func twisError(val uint32) error {
if val == 0 {
return nil
} else if val&nrf.TWIS_ERRORSRC_OVERFLOW_Msk == nrf.TWIS_ERRORSRC_OVERFLOW {
return errI2COverflow
} else if val&nrf.TWIS_ERRORSRC_DNACK_Msk == nrf.TWIS_ERRORSRC_DNACK {
return errI2CAckExpected
} else if val&nrf.TWIS_ERRORSRC_OVERREAD_Msk == nrf.TWIS_ERRORSRC_OVERREAD {
return errI2COverread
}
// All four pins are already in use with other pins.
return 0, ErrInvalidOutputPin
}
// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
func (pwm *PWM) SetInverting(channel uint8, inverting bool) {
ptr := &pwm.channelValues[channel]
if inverting {
ptr.Set(ptr.Get() &^ 0x8000)
} else {
ptr.Set(ptr.Get() | 0x8000)
}
}
// Set updates the channel value. This is used to control the channel duty
// cycle. For example, to set it to a 25% duty cycle, use:
//
// ch.Set(ch.Top() / 4)
//
// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output
// to high, assuming the output isn't inverted.
func (pwm *PWM) Set(channel uint8, value uint32) {
// Update the channel value while retaining the polarity bit.
ptr := &pwm.channelValues[channel]
ptr.Set(ptr.Get()&0x8000 | uint16(value)&0x7fff)
// Start the PWM, if it isn't already running.
pwm.PWM.TASKS_SEQSTART[0].Set(1)
return errI2CBusError
}
+523
View File
@@ -0,0 +1,523 @@
//go:build nrf52 || nrf52840 || nrf52833
package machine
import (
"device/nrf"
"runtime/volatile"
"unsafe"
)
func CPUFrequency() uint32 {
return 64000000
}
// InitADC initializes the registers needed for ADC.
func InitADC() {
return // no specific setup on nrf52 machine.
}
// Configure configures an ADC pin to be able to read analog data.
func (a ADC) Configure(config ADCConfig) {
// Enable ADC.
// The ADC does not consume a noticeable amount of current simply by being
// enabled.
nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos)
// Use fixed resolution of 12 bits.
// TODO: is it useful for users to change this?
nrf.SAADC.RESOLUTION.Set(nrf.SAADC_RESOLUTION_VAL_12bit)
var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESP_Pos |
nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESN_Pos |
nrf.SAADC_CH_CONFIG_REFSEL_Internal<<nrf.SAADC_CH_CONFIG_REFSEL_Pos |
nrf.SAADC_CH_CONFIG_MODE_SE<<nrf.SAADC_CH_CONFIG_MODE_Pos
switch config.Reference {
case 150: // 0.15V
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain4 << nrf.SAADC_CH_CONFIG_GAIN_Pos
case 300: // 0.3V
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain2 << nrf.SAADC_CH_CONFIG_GAIN_Pos
case 600: // 0.6V
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1 << nrf.SAADC_CH_CONFIG_GAIN_Pos
case 1200: // 1.2V
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_2 << nrf.SAADC_CH_CONFIG_GAIN_Pos
case 1800: // 1.8V
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_3 << nrf.SAADC_CH_CONFIG_GAIN_Pos
case 2400: // 2.4V
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_4 << nrf.SAADC_CH_CONFIG_GAIN_Pos
case 3000, 0: // 3.0V (default)
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_5 << nrf.SAADC_CH_CONFIG_GAIN_Pos
case 3600: // 3.6V
configVal |= nrf.SAADC_CH_CONFIG_GAIN_Gain1_6 << nrf.SAADC_CH_CONFIG_GAIN_Pos
default:
// TODO: return an error
}
// Source resistance, according to table 89 on page 364 of the nrf52832 datasheet.
// https://infocenter.nordicsemi.com/pdf/nRF52832_PS_v1.4.pdf
if config.SampleTime <= 3 { // <= 10kΩ
configVal |= nrf.SAADC_CH_CONFIG_TACQ_3us << nrf.SAADC_CH_CONFIG_TACQ_Pos
} else if config.SampleTime <= 5 { // <= 40kΩ
configVal |= nrf.SAADC_CH_CONFIG_TACQ_5us << nrf.SAADC_CH_CONFIG_TACQ_Pos
} else if config.SampleTime <= 10 { // <= 100kΩ
configVal |= nrf.SAADC_CH_CONFIG_TACQ_10us << nrf.SAADC_CH_CONFIG_TACQ_Pos
} else if config.SampleTime <= 15 { // <= 200kΩ
configVal |= nrf.SAADC_CH_CONFIG_TACQ_15us << nrf.SAADC_CH_CONFIG_TACQ_Pos
} else if config.SampleTime <= 20 { // <= 400kΩ
configVal |= nrf.SAADC_CH_CONFIG_TACQ_20us << nrf.SAADC_CH_CONFIG_TACQ_Pos
} else { // <= 800kΩ
configVal |= nrf.SAADC_CH_CONFIG_TACQ_40us << nrf.SAADC_CH_CONFIG_TACQ_Pos
}
// Oversampling configuration.
burst := true
switch config.Samples {
default: // no oversampling
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Bypass)
burst = false
case 2:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over2x)
case 4:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over4x)
case 8:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over8x)
case 16:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over16x)
case 32:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over32x)
case 64:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over64x)
case 128:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over128x)
case 256:
nrf.SAADC.OVERSAMPLE.Set(nrf.SAADC_OVERSAMPLE_OVERSAMPLE_Over256x)
}
if burst {
// BURST=1 is needed when oversampling
configVal |= nrf.SAADC_CH_CONFIG_BURST
}
// Configure channel 0, which is the only channel we use.
nrf.SAADC.CH[0].CONFIG.Set(configVal)
}
// Get returns the current value of a ADC pin in the range 0..0xffff.
func (a ADC) Get() uint16 {
var pwmPin uint32
var rawValue volatile.Register16
switch a.Pin {
case 2:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0
case 3:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1
case 4:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2
case 5:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3
case 28:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4
case 29:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5
case 30:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6
case 31:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7
default:
return 0
}
// Set pin to read.
nrf.SAADC.CH[0].PSELN.Set(pwmPin)
nrf.SAADC.CH[0].PSELP.Set(pwmPin)
// Destination for sample result.
nrf.SAADC.RESULT.PTR.Set(uint32(uintptr(unsafe.Pointer(&rawValue))))
nrf.SAADC.RESULT.MAXCNT.Set(1) // One sample
// Start tasks.
nrf.SAADC.TASKS_START.Set(1)
for nrf.SAADC.EVENTS_STARTED.Get() == 0 {
}
nrf.SAADC.EVENTS_STARTED.Set(0x00)
// Start the sample task.
nrf.SAADC.TASKS_SAMPLE.Set(1)
// Wait until the sample task is done.
for nrf.SAADC.EVENTS_END.Get() == 0 {
}
nrf.SAADC.EVENTS_END.Set(0x00)
// Stop the ADC
nrf.SAADC.TASKS_STOP.Set(1)
for nrf.SAADC.EVENTS_STOPPED.Get() == 0 {
}
nrf.SAADC.EVENTS_STOPPED.Set(0)
value := int16(rawValue.Get())
if value < 0 {
value = 0
}
// Return 16-bit result from 12-bit value.
return uint16(value << 4)
}
// SPI on the NRF.
type SPI struct {
Bus *nrf.SPIM_Type
buf *[1]byte // 1-byte buffer for the Transfer method
}
// There are 3 SPI interfaces on the NRF528xx.
var (
SPI0 = SPI{Bus: nrf.SPIM0, buf: new([1]byte)}
SPI1 = SPI{Bus: nrf.SPIM1, buf: new([1]byte)}
SPI2 = SPI{Bus: nrf.SPIM2, buf: new([1]byte)}
)
// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
Frequency uint32
SCK Pin
SDO Pin
SDI Pin
LSBFirst bool
Mode uint8
}
// Configure is intended to setup the SPI interface.
func (spi SPI) Configure(config SPIConfig) {
// Disable bus to configure it
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled)
// Pick a default frequency.
if config.Frequency == 0 {
config.Frequency = 4000000 // 4MHz
}
// set frequency
var freq uint32
switch {
case config.Frequency >= 8000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M8
case config.Frequency >= 4000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M4
case config.Frequency >= 2000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M2
case config.Frequency >= 1000000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_M1
case config.Frequency >= 500000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K500
case config.Frequency >= 250000:
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K250
default: // below 250kHz, default to the lowest speed available
freq = nrf.SPIM_FREQUENCY_FREQUENCY_K125
}
spi.Bus.FREQUENCY.Set(freq)
var conf uint32
// set bit transfer order
if config.LSBFirst {
conf = (nrf.SPIM_CONFIG_ORDER_LsbFirst << nrf.SPIM_CONFIG_ORDER_Pos)
}
// set mode
switch config.Mode {
case 0:
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
case 1:
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
case 2:
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
case 3:
conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos)
conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos)
default: // to mode
conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos)
conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos)
}
spi.Bus.CONFIG.Set(conf)
// set pins
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
config.SCK = SPI0_SCK_PIN
config.SDO = SPI0_SDO_PIN
config.SDI = SPI0_SDI_PIN
}
spi.Bus.PSEL.SCK.Set(uint32(config.SCK))
spi.Bus.PSEL.MOSI.Set(uint32(config.SDO))
spi.Bus.PSEL.MISO.Set(uint32(config.SDI))
// Re-enable bus now that it is configured.
spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled)
}
// Transfer writes/reads a single byte using the SPI interface.
func (spi SPI) Transfer(w byte) (byte, error) {
buf := spi.buf[:]
buf[0] = w
err := spi.Tx(buf[:], buf[:])
return buf[0], err
}
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous
// write/read interface, there must always be the same number of bytes written
// as bytes read. Therefore, if the number of bytes don't match it will be
// padded until they fit: if len(w) > len(r) the extra bytes received will be
// dropped and if len(w) < len(r) extra 0 bytes will be sent.
func (spi SPI) Tx(w, r []byte) error {
// Unfortunately the hardware (on the nrf52832) only supports up to 255
// bytes in the buffers, so if either w or r is longer than that the
// transfer needs to be broken up in pieces.
// The nrf52840 supports far larger buffers however, which isn't yet
// supported.
for len(r) != 0 || len(w) != 0 {
// Prepare the SPI transfer: set the DMA pointers and lengths.
if len(r) != 0 {
spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
n := uint32(len(r))
if n > 255 {
n = 255
}
spi.Bus.RXD.MAXCNT.Set(n)
r = r[n:]
}
if len(w) != 0 {
spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
n := uint32(len(w))
if n > 255 {
n = 255
}
spi.Bus.TXD.MAXCNT.Set(n)
w = w[n:]
}
// Do the transfer.
// Note: this can be improved by not waiting until the transfer is
// finished if the transfer is send-only (a common case).
spi.Bus.TASKS_START.Set(1)
for spi.Bus.EVENTS_END.Get() == 0 {
}
spi.Bus.EVENTS_END.Set(0)
}
return nil
}
// PWM is one PWM peripheral, which consists of a counter and multiple output
// channels (that can be connected to actual pins). You can set the frequency
// using SetPeriod, but only for all the channels in this PWM peripheral at
// once.
type PWM struct {
PWM *nrf.PWM_Type
channelValues [4]volatile.Register16
}
// Configure enables and configures this PWM.
// On the nRF52 series, the maximum period is around 0.26s.
func (pwm *PWM) Configure(config PWMConfig) error {
// Enable the peripheral.
pwm.PWM.ENABLE.Set(nrf.PWM_ENABLE_ENABLE_Enabled << nrf.PWM_ENABLE_ENABLE_Pos)
// Use up counting only. TODO: allow configuring as up-and-down.
pwm.PWM.MODE.Set(nrf.PWM_MODE_UPDOWN_Up << nrf.PWM_MODE_UPDOWN_Pos)
// Indicate there are four channels that each have a different value.
pwm.PWM.DECODER.Set(nrf.PWM_DECODER_LOAD_Individual<<nrf.PWM_DECODER_LOAD_Pos | nrf.PWM_DECODER_MODE_RefreshCount<<nrf.PWM_DECODER_MODE_Pos)
err := pwm.setPeriod(config.Period, true)
if err != nil {
return err
}
// Set the EasyDMA buffer, which has 4 values (one for each channel).
pwm.PWM.SEQ[0].PTR.Set(uint32(uintptr(unsafe.Pointer(&pwm.channelValues[0]))))
pwm.PWM.SEQ[0].CNT.Set(4)
// SEQ[0] is not yet started, it will be started on the first
// PWMChannel.Set() call.
return nil
}
// SetPeriod updates the period of this PWM peripheral.
// To set a particular frequency, use the following formula:
//
// period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the PWM peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (pwm *PWM) SetPeriod(period uint64) error {
return pwm.setPeriod(period, false)
}
func (pwm *PWM) setPeriod(period uint64, updatePrescaler bool) error {
const maxTop = 0x7fff // 15 bits counter
// The top value is the number of PWM ticks a PWM period takes. It is
// initially picked assuming an unlimited COUNTERTOP and no PWM prescaler.
var top uint64
if period == 0 {
// The period is 0, which means "pick something reasonable for LEDs".
top = maxTop
} else {
// The formula below calculates the following formula, optimized:
// period * (16e6 / 1e9)
// The max frequency (16e6 or 16MHz) is set by the hardware.
top = period * 2 / 125
}
// The ideal PWM period may be larger than would fit in the PWM counter,
// which is only 15 bits (see maxTop). Therefore, try to make the PWM clock
// speed lower with a prescaler to make the top value fit the COUNTERTOP.
if updatePrescaler {
// This function was called during Configure().
switch {
case top <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_1)
case top/2 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_2)
top /= 2
case top/4 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_4)
top /= 4
case top/8 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_8)
top /= 8
case top/16 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_16)
top /= 16
case top/32 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_32)
top /= 32
case top/64 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_64)
top /= 64
case top/128 <= maxTop:
pwm.PWM.PRESCALER.Set(nrf.PWM_PRESCALER_PRESCALER_DIV_128)
top /= 128
default:
return ErrPWMPeriodTooLong
}
} else {
// Do not update the prescaler, but use the already-configured
// prescaler. This is the normal SetPeriod case, where the prescaler
// must not be changed.
prescaler := pwm.PWM.PRESCALER.Get()
switch prescaler {
case nrf.PWM_PRESCALER_PRESCALER_DIV_1:
top /= 1
case nrf.PWM_PRESCALER_PRESCALER_DIV_2:
top /= 2
case nrf.PWM_PRESCALER_PRESCALER_DIV_4:
top /= 4
case nrf.PWM_PRESCALER_PRESCALER_DIV_8:
top /= 8
case nrf.PWM_PRESCALER_PRESCALER_DIV_16:
top /= 16
case nrf.PWM_PRESCALER_PRESCALER_DIV_32:
top /= 32
case nrf.PWM_PRESCALER_PRESCALER_DIV_64:
top /= 64
case nrf.PWM_PRESCALER_PRESCALER_DIV_128:
top /= 128
}
if top > maxTop {
return ErrPWMPeriodTooLong
}
}
pwm.PWM.COUNTERTOP.Set(uint32(top))
// Apparently this is needed to apply the new COUNTERTOP.
pwm.PWM.TASKS_SEQSTART[0].Set(1)
return nil
}
// Top returns the current counter top, for use in duty cycle calculation. It
// will only change with a call to Configure or SetPeriod, otherwise it is
// constant.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to
// pwm.Set (see pwm.Set for more information).
func (pwm *PWM) Top() uint32 {
return pwm.PWM.COUNTERTOP.Get()
}
// Channel returns a PWM channel for the given pin.
func (pwm *PWM) Channel(pin Pin) (uint8, error) {
config := uint32(pin)
for ch := uint8(0); ch < 4; ch++ {
channelConfig := pwm.PWM.PSEL.OUT[ch].Get()
if channelConfig == 0xffffffff {
// Unused channel. Configure it.
pwm.PWM.PSEL.OUT[ch].Set(config)
// Configure the pin (required by the reference manual).
pin.Configure(PinConfig{Mode: PinOutput})
// Set channel to zero and non-inverting.
pwm.channelValues[ch].Set(0x8000)
return ch, nil
} else if channelConfig == config {
// This channel is already configured for this pin.
return ch, nil
}
}
// All four pins are already in use with other pins.
return 0, ErrInvalidOutputPin
}
// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
func (pwm *PWM) SetInverting(channel uint8, inverting bool) {
ptr := &pwm.channelValues[channel]
if inverting {
ptr.Set(ptr.Get() &^ 0x8000)
} else {
ptr.Set(ptr.Get() | 0x8000)
}
}
// Set updates the channel value. This is used to control the channel duty
// cycle. For example, to set it to a 25% duty cycle, use:
//
// ch.Set(ch.Top() / 4)
//
// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output
// to high, assuming the output isn't inverted.
func (pwm *PWM) Set(channel uint8, value uint32) {
// Update the channel value while retaining the polarity bit.
ptr := &pwm.channelValues[channel]
ptr.Set(ptr.Get()&0x8000 | uint16(value)&0x7fff)
// Start the PWM, if it isn't already running.
pwm.PWM.TASKS_SEQSTART[0].Set(1)
}
+106
View File
@@ -0,0 +1,106 @@
//go:build nrf51 || nrf52
package machine
import "device/nrf"
// I2C on the NRF51 and NRF52.
type I2C struct {
Bus *nrf.TWI_Type
mode I2CMode
}
// There are 2 I2C interfaces on the NRF.
var (
I2C0 = &I2C{Bus: nrf.TWI0}
I2C1 = &I2C{Bus: nrf.TWI1}
)
func (i2c *I2C) enableAsController() {
i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled)
}
func (i2c *I2C) enableAsTarget() {
// Not supported on this hardware
}
func (i2c *I2C) disable() {
i2c.Bus.ENABLE.Set(0)
}
// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
// Tricky stop condition.
// After reads, the stop condition is generated implicitly with a shortcut.
// After writes not followed by reads and in the case of errors, stop must be generated explicitly.
i2c.Bus.ADDRESS.Set(uint32(addr))
if len(w) != 0 {
i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing
for _, b := range w {
if err = i2c.writeByte(b); err != nil {
i2c.signalStop()
return
}
}
}
if len(r) != 0 {
// To trigger suspend task when a byte is received
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND)
i2c.Bus.TASKS_STARTRX.Set(1) // re-start transmission for reading
for i := range r { // read each char
if i+1 == len(r) {
// To trigger stop task when last byte is received, set before resume task.
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_STOP)
}
if i > 0 {
i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading
}
if r[i], err = i2c.readByte(); err != nil {
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
i2c.signalStop()
return
}
}
i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled)
}
// Stop explicitly when no reads were executed, stoping unconditionally would be a mistake.
// It may execute after I2C peripheral has already been stopped by the shortcut in the read block,
// so stop task will trigger first thing in a subsequent transaction, hanging it.
if len(r) == 0 {
i2c.signalStop()
}
return
}
// writeByte writes a single byte to the I2C bus and waits for confirmation.
func (i2c *I2C) writeByte(data byte) error {
i2c.Bus.TXD.Set(uint32(data))
for i2c.Bus.EVENTS_TXDSENT.Get() == 0 {
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
i2c.Bus.EVENTS_ERROR.Set(0)
return errI2CBusError
}
}
i2c.Bus.EVENTS_TXDSENT.Set(0)
return nil
}
// readByte reads a single byte from the I2C bus when it is ready.
func (i2c *I2C) readByte() (byte, error) {
for i2c.Bus.EVENTS_RXDREADY.Get() == 0 {
if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 {
i2c.Bus.EVENTS_ERROR.Set(0)
return 0, errI2CBusError
}
}
i2c.Bus.EVENTS_RXDREADY.Set(0)
return byte(i2c.Bus.RXD.Get()), nil
}
-3
View File
@@ -59,9 +59,6 @@ var (
ErrNotConfigured = errors.New("device has not been configured")
)
//go:linkname gosched runtime.Gosched
func gosched()
// PutcharUART writes a byte to the UART synchronously, without using interrupts
// or calling the scheduler
func PutcharUART(u *UART, c byte) {
+21
View File
@@ -4,6 +4,7 @@ package machine
import (
"device/rp"
"runtime/volatile"
"unsafe"
)
@@ -114,3 +115,23 @@ func ChipVersion() uint8 {
version := (chipID & SYSINFO_CHIP_ID_REVISION_BITS) >> SYSINFO_CHIP_ID_REVISION_LSB
return uint8(version)
}
// Single DMA channel. See rp.DMA_Type.
type dmaChannel struct {
READ_ADDR volatile.Register32
WRITE_ADDR volatile.Register32
TRANS_COUNT volatile.Register32
CTRL_TRIG volatile.Register32
_ [12]volatile.Register32 // aliases
}
// Static assignment of DMA channels to peripherals.
// Allocating them statically is good enough for now. If lots of peripherals use
// DMA, these might need to be assigned at runtime.
const (
spi0DMAChannel = iota
spi1DMAChannel
)
// DMA channels usable on the RP2040.
var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA))
+228 -30
View File
@@ -20,10 +20,13 @@ var (
}
)
// The I2C target implementation is based on the C implementation from
// here: https://github.com/vmilea/pico_i2c_slave
// Features: Taken from datasheet.
// Default master mode, with slave mode available (not simulataneously).
// Default slave address of RP2040: 0x055
// Supports 10-bit addressing in Master mode
// Default controller mode, with target mode available (not simulataneously).
// Default target address of RP2040: 0x055
// Supports 10-bit addressing in controller mode
// 16-element transmit buffer
// 16-element receive buffer
// Can be driven from DMA
@@ -45,20 +48,25 @@ type I2CConfig struct {
// SDA/SCL Serial Data and clock pins. Refer to datasheet to see
// which pins match the desired bus.
SDA, SCL Pin
Mode I2CMode
}
type I2C struct {
Bus *rp.I2C0_Type
restartOnNext bool
Bus *rp.I2C0_Type
mode I2CMode
txInProgress bool
}
var (
ErrInvalidI2CBaudrate = errors.New("invalid i2c baudrate")
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
ErrI2CGeneric = errors.New("i2c error")
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
ErrInvalidI2CBaudrate = errors.New("invalid i2c baudrate")
ErrInvalidTgtAddr = errors.New("invalid target i2c address not in 0..0x80 or is reserved")
ErrI2CGeneric = errors.New("i2c error")
ErrRP2040I2CDisable = errors.New("i2c rp2040 peripheral timeout in disable")
errInvalidI2CSDA = errors.New("invalid I2C SDA pin")
errInvalidI2CSCL = errors.New("invalid I2C SCL pin")
ErrI2CAlreadyListening = errors.New("i2c already listening")
ErrI2CWrongMode = errors.New("i2c wrong mode")
ErrI2CUnderflow = errors.New("i2c underflow")
)
// Tx performs a write and then a read transfer placing the result in
@@ -75,11 +83,26 @@ var (
//
// Performs only a write transfer.
func (i2c *I2C) Tx(addr uint16, w, r []byte) error {
if i2c.mode != I2CModeController {
return ErrI2CWrongMode
}
// timeout in microseconds.
const timeout = 40 * 1000 // 40ms is a reasonable time for a real-time system.
return i2c.tx(uint8(addr), w, r, timeout)
}
// Listen starts listening for I2C requests sent to specified address
//
// addr is the address to listen to
func (i2c *I2C) Listen(addr uint16) error {
if i2c.mode != I2CModeTarget {
return ErrI2CWrongMode
}
return i2c.listen(uint8(addr))
}
// Configure initializes i2c peripheral and configures I2C config's pins passed.
// Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040:
//
@@ -212,15 +235,22 @@ func (i2c *I2C) init(config I2CConfig) error {
if err := i2c.disable(); err != nil {
return err
}
i2c.restartOnNext = false
// Configure as a fast-mode master with RepStart support, 7-bit addresses
i2c.Bus.IC_CON.Set((rp.I2C0_IC_CON_SPEED_FAST << rp.I2C0_IC_CON_SPEED_Pos) |
rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE |
rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL) // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status
i2c.mode = config.Mode
// Configure as fast-mode with RepStart support, 7-bit addresses
mode := uint32(rp.I2C0_IC_CON_SPEED_FAST<<rp.I2C0_IC_CON_SPEED_Pos) |
rp.I2C0_IC_CON_IC_RESTART_EN | rp.I2C0_IC_CON_TX_EMPTY_CTRL // sets TX_EMPTY_CTRL to enable TX_EMPTY interrupt status
if config.Mode == I2CModeController {
mode |= rp.I2C0_IC_CON_MASTER_MODE | rp.I2C0_IC_CON_IC_SLAVE_DISABLE
}
i2c.Bus.IC_CON.Set(mode)
// Set FIFO watermarks to 1 to make things simpler. This is encoded by a register value of 0.
i2c.Bus.IC_TX_TL.Set(0)
i2c.Bus.IC_RX_TL.Set(0)
if config.Mode == I2CModeController {
i2c.Bus.IC_TX_TL.Set(0)
i2c.Bus.IC_RX_TL.Set(0)
}
// Always enable the DREQ signalling -- harmless if DMA isn't listening
i2c.Bus.IC_DMA_CR.Set(rp.I2C0_IC_DMA_CR_TDMAE | rp.I2C0_IC_DMA_CR_RDMAE)
@@ -274,7 +304,8 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
i2c.Bus.IC_TAR.Set(uint32(addr))
i2c.enable()
abort := false
var abortReason uint32
var abortReason i2cAbortError
txStop := rxlen == 0
for txCtr := 0; txCtr < txlen; txCtr++ {
if abort {
break
@@ -282,8 +313,8 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
first := txCtr == 0
last := txCtr == txlen-1 && rxlen == 0
i2c.Bus.IC_DATA_CMD.Set(
(boolToBit(first && i2c.restartOnNext) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) |
(boolToBit(last) << rp.I2C0_IC_DATA_CMD_STOP_Pos) |
(boolToBit(first) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) |
(boolToBit(last && txStop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) |
uint32(tx[txCtr]))
// Wait until the transmission of the address/data from the internal
@@ -300,12 +331,14 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
// IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held
// in reset. There the TX FIFO looks like it has no data within
// it, so this bit is set to 1, provided there is activity in the
// master or slave state machines. When there is no longer
// controller or target state machines. When there is no longer
// any activity, then with ic_en=0, this bit is set to 0.
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) {
if ticks() > deadline {
return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else.
}
gosched()
}
abortReason = i2c.getAbortReason()
@@ -322,33 +355,51 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
// to take care of the abort.
for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) {
if ticks() > deadline {
if abort {
return abortReason
}
return errI2CWriteTimeout
}
gosched()
}
i2c.Bus.IC_CLR_STOP_DET.Get()
}
}
// Midway check for abort. Related issue https://github.com/tinygo-org/tinygo/issues/3671.
// The root cause for an abort after writing registers was "tx data no ack" (abort code=8).
// If the abort code was not registered then the whole peripheral would remain in disabled state forever.
abortReason = i2c.getAbortReason()
if abortReason != 0 {
i2c.clearAbortReason()
abort = true
}
rxStart := txlen == 0
if rxlen > 0 && !abort {
for rxCtr := 0; rxCtr < rxlen; rxCtr++ {
first := rxCtr == 0
last := rxCtr == rxlen-1
for i2c.writeAvailable() == 0 {
gosched()
}
i2c.Bus.IC_DATA_CMD.Set(
boolToBit(first && i2c.restartOnNext)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
boolToBit(first && rxStart)<<rp.I2C0_IC_DATA_CMD_RESTART_Pos |
boolToBit(last)<<rp.I2C0_IC_DATA_CMD_STOP_Pos |
rp.I2C0_IC_DATA_CMD_CMD) // -> 1 for read
for !abort && i2c.readAvailable() == 0 {
abortReason = i2c.getAbortReason()
i2c.clearAbortReason()
if abortReason != 0 {
i2c.clearAbortReason()
abort = true
}
if ticks() > deadline {
return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else.
}
gosched()
}
if abort {
break
@@ -368,12 +419,106 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) {
// Address acknowledged, some data not acknowledged
fallthrough
default:
err = makeI2CAbortError(abortReason)
err = abortReason
}
}
return err
}
// listen sets up for async handling of requests on the I2C bus.
func (i2c *I2C) listen(addr uint8) error {
if addr >= 0x80 || isReservedI2CAddr(addr) {
return ErrInvalidTgtAddr
}
err := i2c.disable()
if err != nil {
return err
}
i2c.Bus.IC_SAR.Set(uint32(addr))
i2c.enable()
return nil
}
func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
rxPtr := 0
for {
stat := i2c.Bus.IC_RAW_INTR_STAT.Get()
if stat&rp.I2C0_IC_INTR_MASK_M_RX_FULL != 0 {
b := uint8(i2c.Bus.IC_DATA_CMD.Get())
if rxPtr < len(buf) {
buf[rxPtr] = b
rxPtr++
}
}
// Stop
if stat&rp.I2C0_IC_INTR_MASK_M_STOP_DET != 0 {
if rxPtr > 0 {
return I2CReceive, rxPtr, nil
}
i2c.Bus.IC_CLR_STOP_DET.Get() // clear
return I2CFinish, 0, nil
}
// Start or restart - ignore start, return on restart
if stat&rp.I2C0_IC_INTR_MASK_M_START_DET != 0 {
i2c.Bus.IC_CLR_START_DET.Get() // clear restart
// Restart
if rxPtr > 0 {
return I2CReceive, rxPtr, nil
}
}
// Read request - leave flag set until we start to reply.
if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ != 0 {
return I2CRequest, 0, nil
}
gosched()
}
}
func (i2c *I2C) Reply(buf []byte) error {
txPtr := 0
stat := i2c.Bus.IC_RAW_INTR_STAT.Get()
if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ == 0 {
return ErrI2CWrongMode
}
i2c.Bus.IC_CLR_RD_REQ.Get() // clear restart
// Clear any dangling TX abort
if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
i2c.Bus.IC_CLR_TX_ABRT.Get()
}
for txPtr < len(buf) {
if stat&rp.I2C0_IC_INTR_MASK_M_TX_EMPTY != 0 {
i2c.Bus.IC_DATA_CMD.Set(uint32(buf[txPtr]))
txPtr++
}
// This Tx abort is a normal case - we're sending more
// data than controller wants to receive
if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 {
i2c.Bus.IC_CLR_TX_ABRT.Get()
return nil
}
gosched()
}
return nil
}
// writeAvailable determines non-blocking write space available
//
//go:inline
@@ -401,8 +546,8 @@ func (i2c *I2C) clearAbortReason() {
// getAbortReason reads IC_TX_ABRT_SOURCE register.
//
//go:inline
func (i2c *I2C) getAbortReason() uint32 {
return i2c.Bus.IC_TX_ABRT_SOURCE.Get()
func (i2c *I2C) getAbortReason() i2cAbortError {
return i2cAbortError(i2c.Bus.IC_TX_ABRT_SOURCE.Get())
}
// returns true if RAW_INTR_STAT bits in mask are all set. performs:
@@ -421,9 +566,62 @@ func (b i2cAbortError) Error() string {
return "i2c abort, reason " + itoa.Uitoa(uint(b))
}
//go:inline
func makeI2CAbortError(reason uint32) error {
return i2cAbortError(reason)
func (b i2cAbortError) Reasons() (reasons []string) {
if b == 0 {
return nil
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0 {
reasons = append(reasons, "7-bit address no ack")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR1_NOACK != 0 {
reasons = append(reasons, "10-bit address first byte no ack")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR2_NOACK != 0 {
reasons = append(reasons, "10-bit address second byte no ack")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0 {
reasons = append(reasons, "tx data no ack")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_NOACK != 0 {
reasons = append(reasons, "general call no ack")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_READ != 0 {
reasons = append(reasons, "general call read")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_ACKDET != 0 {
reasons = append(reasons, "high speed ack detect")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_ACKDET != 0 {
reasons = append(reasons, "start byte ack detect")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_NORSTRT != 0 {
reasons = append(reasons, "high speed no restart")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_NORSTRT != 0 {
reasons = append(reasons, "start byte no restart")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10B_RD_NORSTRT != 0 {
reasons = append(reasons, "10-bit read no restart")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_MASTER_DIS != 0 {
reasons = append(reasons, "master disabled")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ARB_LOST != 0 {
reasons = append(reasons, "arbitration lost")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVFLUSH_TXFIFO != 0 {
reasons = append(reasons, "slave flush tx fifo")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLV_ARBLOST != 0 {
reasons = append(reasons, "slave arbitration lost")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVRD_INTX != 0 {
reasons = append(reasons, "slave read while inactive")
}
if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_USER_ABRT != 0 {
reasons = append(reasons, "user abort")
}
return reasons
}
//go:inline
+3 -1
View File
@@ -260,7 +260,9 @@ func (pwm *pwmGroup) setPeriod(period uint64) error {
maxTop = math.MaxUint16
// start algorithm at 95% Top. This allows us to undershoot period with prescale.
topStart = 95 * maxTop / 100
milliseconds = 1_000_000_000
nanosecond = 1 // 1e-9 [s]
microsecond = 1000 * nanosecond // 1e-6 [s]
milliseconds = 1000 * microsecond // 1e-3 [s]
// Maximum Period is 268369920ns on rp2040, given by (16*255+15)*8*(1+0xffff)*(1+1)/16
// With no phase shift max period is half of this value.
maxPeriod = 268 * milliseconds
+5 -5
View File
@@ -195,7 +195,7 @@ func (f flashBlockDevice) Size() int64 {
return int64(FlashDataEnd() - FlashDataStart())
}
const writeBlockSize = 4
const writeBlockSize = 1 << 8
// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
@@ -229,19 +229,19 @@ func (f flashBlockDevice) EraseBlocks(start, length int64) error {
state := interrupt.Disable()
defer interrupt.Restore(state)
C.flash_erase_blocks(C.uint32_t(address), C.ulong(length))
C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize()))
return nil
}
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
overflow := int64(len(p)) % f.WriteBlockSize()
if overflow == 0 {
return p
}
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
return append(p, padding...)
}
+50 -34
View File
@@ -5,6 +5,7 @@ package machine
import (
"device/rp"
"errors"
"unsafe"
)
// SPI on the RP2040
@@ -47,9 +48,6 @@ type SPI struct {
Bus *rp.SPI0_Type
}
// time to wait on a transaction before dropping. Unit in Microseconds for compatibility with ticks().
const _SPITimeout = 10 * 1000 // 10 ms
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
// interface, there must always be the same number of bytes written as bytes read.
// The Tx method knows about this, and offers a few different ways of calling it.
@@ -95,26 +93,19 @@ func (spi SPI) Tx(w, r []byte) (err error) {
// Write a single byte and read a single byte from TX/RX FIFO.
func (spi SPI) Transfer(w byte) (byte, error) {
var deadline = ticks() + _SPITimeout
for !spi.isWritable() {
if ticks() > deadline {
return 0, ErrSPITimeout
}
}
spi.Bus.SSPDR.Set(uint32(w))
for !spi.isReadable() {
if ticks() > deadline {
return 0, ErrSPITimeout
}
}
return uint8(spi.Bus.SSPDR.Get()), nil
}
func (spi SPI) SetBaudRate(br uint32) error {
const freqin uint32 = 125 * MHz
const maxBaud uint32 = 62.5 * MHz // max output frequency is 62.5MHz on rp2040, see page 507
const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527.
// Find smallest prescale value which puts output frequency in range of
// post-divide. Prescale is an even number from 2 to 254 inclusive.
var prescale, postdiv uint32
@@ -146,7 +137,7 @@ func (spi SPI) GetBaudRate() uint32 {
}
// Configure is intended to setup/initialize the SPI interface.
// Default baudrate of 115200 is used if Frequency == 0. Default
// Default baudrate of 4MHz is used if Frequency == 0. Default
// word length (data bits) is 8.
// Below is a list of GPIO pins corresponding to SPI0 bus on the rp2040:
//
@@ -162,7 +153,7 @@ func (spi SPI) GetBaudRate() uint32 {
//
// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure.
func (spi SPI) Configure(config SPIConfig) error {
const defaultBaud uint32 = 115200
const defaultBaud uint32 = 4 * MHz
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
// set default pins if config zero valued or invalid clock pin supplied.
switch spi.Bus {
@@ -298,27 +289,57 @@ func (spi SPI) isBusy() bool {
// tx writes buffer to SPI ignoring Rx.
func (spi SPI) tx(tx []byte) error {
var deadline = ticks() + _SPITimeout
// Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX
// is full, PL022 inhibits RX pushes, and sets a sticky flag on
// push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set.
for i := range tx {
for !spi.isWritable() {
if ticks() > deadline {
return ErrSPITimeout
}
}
spi.Bus.SSPDR.Set(uint32(tx[i]))
if len(tx) == 0 {
// We don't have to do anything.
// This avoids a panic in &tx[0] when len(tx) == 0.
return nil
}
// Pick the DMA channel reserved for this SPI peripheral.
var ch *dmaChannel
var dreq uint32
if spi.Bus == rp.SPI0 {
ch = &dmaChannels[spi0DMAChannel]
dreq = 16 // DREQ_SPI0_TX
} else { // SPI1
ch = &dmaChannels[spi1DMAChannel]
dreq = 18 // DREQ_SPI1_TX
}
// Configure the DMA peripheral as follows:
// - set read address, write address, and number of transfer units (bytes)
// - increment read address (in memory), don't increment write address (SSPDR)
// - set data size to single bytes
// - set the DREQ so that the DMA will fill the SPI FIFO as needed
// - start the transfer
ch.READ_ADDR.Set(uint32(uintptr(unsafe.Pointer(&tx[0]))))
ch.WRITE_ADDR.Set(uint32(uintptr(unsafe.Pointer(&spi.Bus.SSPDR))))
ch.TRANS_COUNT.Set(uint32(len(tx)))
ch.CTRL_TRIG.Set(rp.DMA_CH0_CTRL_TRIG_INCR_READ |
rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE<<rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos |
dreq<<rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos |
rp.DMA_CH0_CTRL_TRIG_EN)
// Wait until the transfer is complete.
// TODO: do this more efficiently:
// - Add a new API to start the transfer, without waiting for it to
// complete. This way, the CPU can do something useful while the
// transfer is in progress.
// - If we have to wait, do so by waiting for an interrupt and blocking
// this goroutine until finished (so that other goroutines can run or
// the CPU can go to sleep).
for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 {
}
// We didn't read any result values, which means the RX FIFO has likely
// overflown. We have to clean up this mess now.
// Drain RX FIFO, then wait for shifting to finish (which may be *after*
// TX FIFO drains), then drain RX FIFO again
for spi.isReadable() {
spi.Bus.SSPDR.Get()
}
for spi.isBusy() {
if ticks() > deadline {
return ErrSPITimeout
}
}
for spi.isReadable() {
spi.Bus.SSPDR.Get()
@@ -333,7 +354,6 @@ func (spi SPI) tx(tx []byte) error {
// Generally this can be 0, but some devices require a specific value here,
// e.g. SD cards expect 0xff
func (spi SPI) rx(rx []byte, txrepeat byte) error {
var deadline = ticks() + _SPITimeout
plen := len(rx)
const fifoDepth = 8 // see txrx
var rxleft, txleft = plen, plen
@@ -345,10 +365,7 @@ func (spi SPI) rx(rx []byte, txrepeat byte) error {
if rxleft != 0 && spi.isReadable() {
rx[plen-rxleft] = uint8(spi.Bus.SSPDR.Get())
rxleft--
continue // if reading succesfully in rx there is no need to check deadline.
}
if ticks() > deadline {
return ErrSPITimeout
continue
}
}
return nil
@@ -357,7 +374,6 @@ func (spi SPI) rx(rx []byte, txrepeat byte) error {
// Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst.
// Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit)
func (spi SPI) txrx(tx, rx []byte) error {
var deadline = ticks() + _SPITimeout
plen := len(tx)
if plen != len(rx) {
return ErrTxInvalidSliceSize
@@ -366,7 +382,7 @@ func (spi SPI) txrx(tx, rx []byte) error {
// else FIFO will overflow if this code is heavily interrupted.
const fifoDepth = 8
var rxleft, txleft = plen, plen
for (txleft != 0 || rxleft != 0) && ticks() <= deadline {
for txleft != 0 || rxleft != 0 {
if txleft != 0 && spi.isWritable() && rxleft < txleft+fifoDepth {
spi.Bus.SSPDR.Set(uint32(tx[plen-txleft]))
txleft--
+4 -4
View File
@@ -92,13 +92,13 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error {
// pad data if needed so it is long enough for correct byte alignment on writes.
func (f flashBlockDevice) pad(p []byte) []byte {
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
if paddingNeeded == 0 {
overflow := int64(len(p)) % f.WriteBlockSize()
if overflow == 0 {
return p
}
padded := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
return append(p, padded...)
padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow))
return append(p, padding...)
}
const memoryStart = 0x08000000
+13
View File
@@ -0,0 +1,13 @@
package machine
import (
_ "unsafe"
)
//
// This file provides access to runtime package that would not otherwise
// be permitted due to linker dependencies.
//
//go:linkname gosched runtime.Gosched
func gosched()
+24 -20
View File
@@ -3,10 +3,10 @@
package machine
import (
"bytes"
"encoding/binary"
"errors"
"machine/usb"
"machine/usb/descriptor"
"errors"
)
type USBDevice struct {
@@ -29,7 +29,7 @@ type Serialer interface {
RTS() bool
}
var usbDescriptor = usb.DescriptorCDC
var usbDescriptor = descriptor.CDC
var usbDescriptorConfig uint8 = usb.DescriptorConfigCDC
@@ -72,7 +72,7 @@ func usbProduct() string {
// binary.
func strToUTF16LEDescriptor(in string, out []byte) {
out[0] = byte(len(out))
out[1] = usb.STRING_DESCRIPTOR_TYPE
out[1] = descriptor.TypeString
for i, rune := range in {
out[(i<<1)+2] = byte(rune)
out[(i<<1)+3] = 0
@@ -88,7 +88,7 @@ var (
)
var (
usbEndpointDescriptors [usb.NumberOfEndpoints]usb.DeviceDescriptor
usbEndpointDescriptors [usb.NumberOfEndpoints]descriptor.Device
isEndpointHalt = false
isRemoteWakeUpEnabled = false
@@ -134,27 +134,27 @@ var (
// can be requested by the host.
func sendDescriptor(setup usb.Setup) {
switch setup.WValueH {
case usb.CONFIGURATION_DESCRIPTOR_TYPE:
case descriptor.TypeConfiguration:
sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength)
return
case usb.DEVICE_DESCRIPTOR_TYPE:
case descriptor.TypeDevice:
// composite descriptor
switch {
case (usbDescriptorConfig & usb.DescriptorConfigHID) > 0:
usbDescriptor = usb.DescriptorCDCHID
usbDescriptor = descriptor.CDCHID
case (usbDescriptorConfig & usb.DescriptorConfigMIDI) > 0:
usbDescriptor = usb.DescriptorCDCMIDI
usbDescriptor = descriptor.CDCMIDI
case (usbDescriptorConfig & usb.DescriptorConfigJoystick) > 0:
usbDescriptor = usb.DescriptorCDCJoystick
usbDescriptor = descriptor.CDCJoystick
default:
usbDescriptor = usb.DescriptorCDC
usbDescriptor = descriptor.CDC
}
usbDescriptor.Configure(usbVendorID(), usbProductID())
sendUSBPacket(0, usbDescriptor.Device, setup.WLength)
return
case usb.STRING_DESCRIPTOR_TYPE:
case descriptor.TypeString:
switch setup.WValueL {
case 0:
usb_trans_buffer[0] = 0x04
@@ -178,12 +178,12 @@ func sendDescriptor(setup usb.Setup) {
SendZlp()
}
return
case usb.HID_REPORT_TYPE:
case descriptor.TypeHIDReport:
if h, ok := usbDescriptor.HID[setup.WIndex]; ok {
sendUSBPacket(0, h, setup.WLength)
return
}
case usb.DEVICE_QUALIFIER:
case descriptor.TypeDeviceQualifier:
// skip
default:
}
@@ -302,11 +302,15 @@ func EnableMIDI(txHandler func(), rxHandler func([]byte), setupHandler func(usb.
// EnableJoystick enables HID. This function must be executed from the init().
func EnableJoystick(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool, hidDesc []byte) {
idx := bytes.Index(usb.DescriptorCDCJoystick.Configuration, []byte{
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22,
})
binary.LittleEndian.PutUint16(usb.DescriptorCDCJoystick.Configuration[idx+7:idx+9], uint16(len(hidDesc)))
usb.DescriptorCDCJoystick.HID[2] = hidDesc
class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes())
if err != nil {
// TODO: some way to notify about error
return
}
class.ClassLength(uint16(len(hidDesc)))
descriptor.CDCJoystick.HID[2] = hidDesc
usbDescriptorConfig |= usb.DescriptorConfigJoystick
endPoints[usb.HID_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut)
usbRxHandler[usb.HID_ENDPOINT_OUT] = rxHandler
+2
View File
@@ -0,0 +1,2 @@
// package adc is for USB Audio Device Class devices.
package adc
+2
View File
@@ -0,0 +1,2 @@
// package cdc is for USB Communication Device Class devices.
package cdc
-179
View File
@@ -1,179 +0,0 @@
package usb
import "runtime/volatile"
// DeviceDescBank is the USB device endpoint descriptor.
type DeviceDescBank struct {
ADDR volatile.Register32
PCKSIZE volatile.Register32
EXTREG volatile.Register16
STATUS_BK volatile.Register8
_reserved [5]volatile.Register8
}
type DeviceDescriptor struct {
DeviceDescBank [2]DeviceDescBank
}
type Descriptor struct {
Device []byte
Configuration []byte
HID map[uint16][]byte
}
func (d *Descriptor) Configure(idVendor, idProduct uint16) {
d.Device[8] = byte(idVendor)
d.Device[9] = byte(idVendor >> 8)
d.Device[10] = byte(idProduct)
d.Device[11] = byte(idProduct >> 8)
d.Configuration[2] = byte(len(d.Configuration))
d.Configuration[3] = byte(len(d.Configuration) >> 8)
}
var DescriptorCDC = Descriptor{
Device: []byte{
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x86, 0x28, 0x2d, 0x80, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01,
},
Configuration: []byte{
0x09, 0x02, 0x4b, 0x00, 0x02, 0x01, 0x00, 0xa0, 0x32,
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
0x05, 0x24, 0x00, 0x10, 0x01,
0x04, 0x24, 0x02, 0x06,
0x05, 0x24, 0x06, 0x00, 0x01,
0x05, 0x24, 0x01, 0x01, 0x01,
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x10,
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
},
}
var DescriptorCDCHID = Descriptor{
Device: []byte{
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x86, 0x28, 0x2d, 0x80, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01,
},
Configuration: []byte{
0x09, 0x02, 0x64, 0x00, 0x03, 0x01, 0x00, 0xa0, 0x32,
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
0x05, 0x24, 0x00, 0x10, 0x01,
0x04, 0x24, 0x02, 0x06,
0x05, 0x24, 0x06, 0x00, 0x01,
0x05, 0x24, 0x01, 0x01, 0x01,
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x10,
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
0x09, 0x04, 0x02, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00,
0x09, 0x21, 0x01, 0x01, 0x00, 0x01, 0x22, 0x7E, 0x00,
0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x01,
},
HID: map[uint16][]byte{
2: []byte{
// keyboard and mouse
0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x02, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00,
0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x03, 0x95, 0x06,
0x75, 0x08, 0x15, 0x00, 0x25, 0xFF, 0x05, 0x07, 0x19, 0x00, 0x29, 0xFF, 0x81, 0x00, 0xc0, 0x05,
0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x85, 0x01, 0x05, 0x09, 0x19, 0x01, 0x29,
0x03, 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, 0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81,
0x03, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95,
0x03, 0x81, 0x06, 0xc0, 0xc0,
0x05, 0x0C, // Usage Page (Consumer)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report ID (3)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x1F, // Logical Maximum (8191)
0x19, 0x00, // Usage Minimum (Unassigned)
0x2A, 0xFF, 0x1F, // Usage Maximum (0x1FFF)
0x75, 0x10, // Report Size (16)
0x95, 0x01, // Report Count (1)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
},
},
}
var DescriptorCDCMIDI = Descriptor{
Device: []byte{
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x86, 0x28, 0x2d, 0x80, 0x00, 0x01, 0x01, 0x02, 0x03, 0x01,
},
Configuration: []byte{
0x09, 0x02, 0xaf, 0x00, 0x04, 0x01, 0x00, 0xa0, 0x32,
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
0x05, 0x24, 0x00, 0x10, 0x01,
0x04, 0x24, 0x02, 0x06,
0x05, 0x24, 0x06, 0x00, 0x01,
0x05, 0x24, 0x01, 0x01, 0x01,
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x10,
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
0x08, 0x0b, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00,
0x09, 0x04, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x03,
0x09, 0x04, 0x03, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00,
0x07, 0x24, 0x01, 0x00, 0x01, 0x41, 0x00,
0x06, 0x24, 0x02, 0x01, 0x01, 0x00,
0x06, 0x24, 0x02, 0x02, 0x02, 0x00,
0x09, 0x24, 0x03, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00,
0x09, 0x24, 0x03, 0x02, 0x04, 0x01, 0x01, 0x01, 0x00,
0x09, 0x05, 0x07, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
0x05, 0x25, 0x01, 0x01, 0x01,
0x09, 0x05, 0x86, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
0x05, 0x25, 0x01, 0x01, 0x03,
},
}
var DescriptorCDCJoystick = Descriptor{
Device: []byte{
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40,
0x41, 0x23, 0x36, 0x80, 0x00, 0x01, 0x01, 0x02,
0x03, 0x01,
},
Configuration: []byte{
// Configuration Descriptor Header
0x09, 0x02,
0x6b, 0x00, // Total Length: 0x006b(107)
0x03, 0x01, 0x00, 0xa0, 0xfa,
// InterfaceAssociation Descriptoy
0x08, 0x0b, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,
// InterfaceSescriptor(CDC-Ctrl)
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x00, 0x00,
// Communication Descriptor
0x05, 0x24, 0x00, 0x10, 0x01,
// Communication Descriptor
0x05, 0x24, 0x01, 0x01, 0x01,
// Communication Descriptor
0x04, 0x24, 0x02, 0x06,
// Communication Descriptor
0x05, 0x24, 0x06, 0x00, 0x01,
// ENDPOINT Descriptor
0x07, 0x05, 0x81, 0x03, 0x10, 0x00, 0x40,
// InterfaceSescriptor(CDC-Data)
0x09, 0x04, 0x01, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00,
// ENDPOINT Descriptor
0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
// ENDPOINT Descriptor
0x07, 0x05, 0x83, 0x02, 0x40, 0x00, 0x00,
// InterfaceSescriptor(HID)
0x09, 0x04, 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00,
// HID Descriptor
0x09, // bLength: 9
0x21, // bDescriptorType: 0x21(HID)
0x11, 0x01, // bcdHID: 0x111
0x00, // bCountryCode: Not Supported
0x01, // bNumDescriptors: 1
0x22, // Type: HID Report
0x00, 0x00, // Length
// ENDPOINT Descriptor
0x07, 0x05, 0x84, 0x03, 0x40, 0x00, 0x01,
// ENDPOINT Descriptor
0x07, 0x05, 0x05, 0x03, 0x40, 0x00, 0x01,
},
HID: map[uint16][]byte{},
}
+168
View File
@@ -0,0 +1,168 @@
package descriptor
const (
cdcFunctionalHeader = 0
cdcFunctionalCallManagement = 0x1
cdcFunctionalACM = 0x2
cdcFunctionalDirect = 0x3
cdcFunctionalRinger = 0x4
cdcFunctionalCall = 0x5
cdcFunctionalUnion = 0x6
cdcFunctionalCountry = 0x7
cdcFunctionalOperational = 0x8
cdcFunctionalUSB = 0x9
cdcFunctionalNetwork = 0xa
cdcFunctionalProtocol = 0xb
cdcFunctionalExtension = 0xc
cdcFunctionalMulti = 0xd
cdcFunctionalCAPI = 0xe
cdcFunctionalEthernet = 0xf
cdcFunctionalATM = 0x10
)
var classSpecificCDCHeader = [classSpecificTypeLen]byte{
classSpecificTypeLen,
TypeClassSpecific,
cdcFunctionalHeader,
0x10, //
0x1, //
}
var ClassSpecificCDCHeader = ClassSpecificType{
data: classSpecificCDCHeader[:],
}
var classSpecificCDCCallManagement = [classSpecificTypeLen]byte{
classSpecificTypeLen,
TypeClassSpecific,
cdcFunctionalCallManagement,
0x0, //
0x1, //
}
var ClassSpecificCDCCallManagement = ClassSpecificType{
data: classSpecificCDCCallManagement[:],
}
var classSpecificCDCACM = [classSpecificTypeLen]byte{
4,
TypeClassSpecific,
cdcFunctionalACM,
0x2, //
}
var ClassSpecificCDCACM = ClassSpecificType{
data: classSpecificCDCACM[:],
}
var classSpecificCDCUnion = [classSpecificTypeLen]byte{
classSpecificTypeLen,
TypeClassSpecific,
cdcFunctionalUnion,
0x0, //
0x1, //
}
var ClassSpecificCDCUnion = ClassSpecificType{
data: classSpecificCDCUnion[:],
}
var interfaceAssociationCDC = [interfaceAssociationTypeLen]byte{
interfaceAssociationTypeLen,
TypeInterfaceAssociation,
0x00, // FirstInterface
0x02, // InterfaceCount
0x02, // FunctionClass
0x02, // FunctionSubClass
0x01, // FunctionProtocol
0x00, // Function
}
var InterfaceAssociationCDC = InterfaceAssociationType{
data: interfaceAssociationCDC[:],
}
var deviceCDC = [deviceTypeLen]byte{
deviceTypeLen,
TypeDevice,
0x00, 0x02, // USB version
0xef, // device class
0x02, // device subclass
0x01, // protocol
0x40, // maxpacketsize
0x86, 0x28, // vendor id
0x2d, 0x80, // product id
0x00, 0x01, // device
0x01, // manufacturer
0x02, // product
0x03, // SerialNumber
0x01, // NumConfigurations
}
var DeviceCDC = DeviceType{
data: deviceCDC[:],
}
var configurationCDC = [configurationTypeLen]byte{
configurationTypeLen,
TypeConfiguration,
0x4b, 0x00, // adjust length as needed
0x02, // number of interfaces
0x01, // configuration value
0x00, // index to string description
0xa0, // attributes
0x32, // maxpower
}
var ConfigurationCDC = ConfigurationType{
data: configurationCDC[:],
}
var interfaceCDCControl = [interfaceTypeLen]byte{
interfaceTypeLen,
TypeInterface,
0x00, // InterfaceNumber
0x00, // AlternateSetting
0x01, // NumEndpoints
0x02, // InterfaceClass
0x02, // InterfaceSubClass
0x01, // InterfaceProtocol
0x00, // Interface
}
var InterfaceCDCControl = InterfaceType{
data: interfaceCDCControl[:],
}
var interfaceCDCData = [interfaceTypeLen]byte{
interfaceTypeLen,
TypeInterface,
0x01, // InterfaceNumber
0x00, // AlternateSetting
0x02, // NumEndpoints
0x0a, // InterfaceClass
0x00, // InterfaceSubClass
0x00, // InterfaceProtocol
0x00, // Interface
}
var InterfaceCDCData = InterfaceType{
data: interfaceCDCData[:],
}
var CDC = Descriptor{
Device: DeviceCDC.Bytes(),
Configuration: Append([][]byte{
ConfigurationCDC.Bytes(),
InterfaceAssociationCDC.Bytes(),
InterfaceCDCControl.Bytes(),
ClassSpecificCDCHeader.Bytes(),
ClassSpecificCDCCallManagement.Bytes(),
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
EndpointEP1IN.Bytes(),
InterfaceCDCData.Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
}),
}
@@ -0,0 +1,17 @@
package descriptor
const (
classSpecificTypeLen = 5
)
type ClassSpecificType struct {
data []byte
}
func (d ClassSpecificType) Bytes() []byte {
return d.data[:d.data[0]]
}
func (d ClassSpecificType) Length(v uint8) {
d.data[0] = byte(v)
}
@@ -0,0 +1,49 @@
package descriptor
import (
"encoding/binary"
)
const (
configurationTypeLen = 9
)
type ConfigurationType struct {
data []byte
}
func (d ConfigurationType) Bytes() []byte {
return d.data
}
func (d ConfigurationType) Length(v uint8) {
d.data[0] = byte(v)
}
func (d ConfigurationType) Type(v uint8) {
d.data[1] = byte(v)
}
func (d ConfigurationType) TotalLength(v uint16) {
binary.LittleEndian.PutUint16(d.data[2:4], v)
}
func (d ConfigurationType) NumInterfaces(v uint8) {
d.data[4] = byte(v)
}
func (d ConfigurationType) ConfigurationValue(v uint8) {
d.data[5] = byte(v)
}
func (d ConfigurationType) Configuration(v uint8) {
d.data[6] = byte(v)
}
func (d ConfigurationType) Attributes(v uint8) {
d.data[7] = byte(v)
}
func (d ConfigurationType) MaxPower(v uint8) {
d.data[8] = byte(v)
}
+63
View File
@@ -0,0 +1,63 @@
package descriptor
import (
"runtime/volatile"
)
const (
TypeDevice = 0x1
TypeConfiguration = 0x2
TypeString = 0x3
TypeInterface = 0x4
TypeEndpoint = 0x5
TypeDeviceQualifier = 0x6
TypeInterfaceAssociation = 0xb
TypeClassHID = 0x21
TypeHIDReport = 0x22
TypeClassSpecific = 0x24
TypeClassSpecificEndpoint = 0x25
)
// DeviceDescBank is the USB device endpoint .
type DeviceDescBank struct {
ADDR volatile.Register32
PCKSIZE volatile.Register32
EXTREG volatile.Register16
STATUS_BK volatile.Register8
_reserved [5]volatile.Register8
}
type Device struct {
DeviceDescBank [2]DeviceDescBank
}
type Descriptor struct {
Device []byte
Configuration []byte
HID map[uint16][]byte
}
func (d *Descriptor) Configure(idVendor, idProduct uint16) {
dev := DeviceType{d.Device}
dev.VendorID(idVendor)
dev.ProductID(idProduct)
conf := ConfigurationType{d.Configuration}
conf.TotalLength(uint16(len(d.Configuration)))
}
func Append[T any](slices [][]T) []T {
var size, pos int
for _, s := range slices {
size += len(s)
}
result := make([]T, size)
for _, s := range slices {
pos += copy(result[pos:], s)
}
return result
}
+73
View File
@@ -0,0 +1,73 @@
package descriptor
import (
"encoding/binary"
)
const (
deviceTypeLen = 18
)
type DeviceType struct {
data []byte
}
func (d DeviceType) Bytes() []byte {
return d.data
}
func (d DeviceType) Length(v uint8) {
d.data[0] = byte(v)
}
func (d DeviceType) Type(v uint8) {
d.data[1] = byte(v)
}
func (d DeviceType) USB(v uint16) {
binary.LittleEndian.PutUint16(d.data[2:4], v)
}
func (d DeviceType) DeviceClass(v uint8) {
d.data[4] = byte(v)
}
func (d DeviceType) DeviceSubClass(v uint8) {
d.data[5] = byte(v)
}
func (d DeviceType) DeviceProtocol(v uint8) {
d.data[6] = byte(v)
}
func (d DeviceType) MaxPacketSize0(v uint8) {
d.data[7] = byte(v)
}
func (d DeviceType) VendorID(v uint16) {
binary.LittleEndian.PutUint16(d.data[8:10], v)
}
func (d DeviceType) ProductID(v uint16) {
binary.LittleEndian.PutUint16(d.data[10:12], v)
}
func (d DeviceType) Device(v uint16) {
binary.LittleEndian.PutUint16(d.data[12:14], v)
}
func (d DeviceType) Manufacturer(v uint8) {
d.data[14] = byte(v)
}
func (d DeviceType) Product(v uint8) {
d.data[15] = byte(v)
}
func (d DeviceType) SerialNumber(v uint8) {
d.data[16] = byte(v)
}
func (d DeviceType) NumConfigurations(v uint8) {
d.data[17] = byte(v)
}
+3
View File
@@ -0,0 +1,3 @@
// package descriptor is for the USB descriptor definitions.
// For the actual implementations, see the individual packages.
package descriptor
+111
View File
@@ -0,0 +1,111 @@
package descriptor
import (
"encoding/binary"
)
var endpointEP1IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x81, // EndpointAddress
0x03, // Attributes
0x10, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x10, // Interval
}
var EndpointEP1IN = EndpointType{
data: endpointEP1IN[:],
}
var endpointEP2OUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x02, // EndpointAddress
0x02, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointEP2OUT = EndpointType{
data: endpointEP2OUT[:],
}
var endpointEP3IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x83, // EndpointAddress
0x02, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointEP3IN = EndpointType{
data: endpointEP3IN[:],
}
var endpointEP4IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x84, // EndpointAddress
0x03, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x01, // Interval
}
var EndpointEP4IN = EndpointType{
data: endpointEP4IN[:],
}
var endpointEP5OUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x05, // EndpointAddress
0x03, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x01, // Interval
}
var EndpointEP5OUT = EndpointType{
data: endpointEP5OUT[:],
}
const (
endpointTypeLen = 7
)
type EndpointType struct {
data []byte
}
func (d EndpointType) Bytes() []byte {
return d.data
}
func (d EndpointType) Length(v uint8) {
d.data[0] = byte(v)
}
func (d EndpointType) Type(v uint8) {
d.data[1] = byte(v)
}
func (d EndpointType) EndpointAddress(v uint8) {
d.data[2] = byte(v)
}
func (d EndpointType) Attributes(v uint8) {
d.data[3] = byte(v)
}
func (d EndpointType) MaxPacketSize(v uint16) {
binary.LittleEndian.PutUint16(d.data[4:6], v)
}
func (d EndpointType) Interval(v uint8) {
d.data[6] = byte(v)
}
+205
View File
@@ -0,0 +1,205 @@
package descriptor
import (
"bytes"
"encoding/binary"
"errors"
)
var configurationCDCHID = [configurationTypeLen]byte{
configurationTypeLen,
TypeConfiguration,
0x64, 0x00, // adjust length as needed
0x03, // number of interfaces
0x01, // configuration value
0x00, // index to string description
0xa0, // attributes
0x32, // maxpower
}
var ConfigurationCDCHID = ConfigurationType{
data: configurationCDCHID[:],
}
var interfaceHID = [interfaceTypeLen]byte{
interfaceTypeLen,
TypeInterface,
0x02, // InterfaceNumber
0x00, // AlternateSetting
0x01, // NumEndpoints
0x03, // InterfaceClass
0x00, // InterfaceSubClass
0x00, // InterfaceProtocol
0x00, // Interface
}
var InterfaceHID = InterfaceType{
data: interfaceHID[:],
}
const (
ClassHIDTypeLen = 9
)
type ClassHIDType struct {
data []byte
}
func (d ClassHIDType) Bytes() []byte {
return d.data[:]
}
func (d ClassHIDType) Length(v uint8) {
d.data[0] = byte(v)
}
func (d ClassHIDType) Type(v uint8) {
d.data[1] = byte(v)
}
func (d ClassHIDType) HID(v uint16) {
binary.LittleEndian.PutUint16(d.data[2:4], v)
}
func (d ClassHIDType) CountryCode(v uint8) {
d.data[4] = byte(v)
}
func (d ClassHIDType) NumDescriptors(v uint8) {
d.data[5] = byte(v)
}
func (d ClassHIDType) ClassType(v uint8) {
d.data[6] = byte(v)
}
func (d ClassHIDType) ClassLength(v uint16) {
binary.LittleEndian.PutUint16(d.data[7:9], v)
}
var errNoClassHIDFound = errors.New("no classHID found")
// FindClassHIDType tries to find the ClassHID class in the descriptor.
func FindClassHIDType(des, class []byte) (ClassHIDType, error) {
if len(des) < ClassHIDTypeLen || len(class) == 0 {
return ClassHIDType{}, errNoClassHIDFound
}
// search only for ClassHIDType without the ClassLength,
// in case it has already been set.
idx := bytes.Index(des, class[:ClassHIDTypeLen-2])
if idx == -1 {
return ClassHIDType{}, errNoClassHIDFound
}
return ClassHIDType{data: des[idx : idx+ClassHIDTypeLen]}, nil
}
var classHID = [ClassHIDTypeLen]byte{
ClassHIDTypeLen,
TypeClassHID,
0x11, // HID version L
0x01, // HID version H
0x00, // CountryCode
0x01, // NumDescriptors
0x22, // ClassType
0x7E, // ClassLength L
0x00, // ClassLength H
}
var ClassHID = ClassHIDType{
data: classHID[:],
}
var CDCHID = Descriptor{
Device: DeviceCDC.Bytes(),
Configuration: Append([][]byte{
ConfigurationCDCHID.Bytes(),
InterfaceAssociationCDC.Bytes(),
InterfaceCDCControl.Bytes(),
ClassSpecificCDCHeader.Bytes(),
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
ClassSpecificCDCCallManagement.Bytes(),
EndpointEP1IN.Bytes(),
InterfaceCDCData.Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
InterfaceHID.Bytes(),
ClassHID.Bytes(),
EndpointEP4IN.Bytes(),
}),
HID: map[uint16][]byte{
2: Append([][]byte{
HIDUsagePageGenericDesktop,
HIDUsageDesktopKeyboard,
HIDCollectionApplication,
HIDReportID(2),
HIDUsagePageKeyboard,
HIDUsageMinimum(224),
HIDUsageMaximum(231),
HIDLogicalMinimum(0),
HIDLogicalMaximum(1),
HIDReportSize(1),
HIDReportCount(8),
HIDInputDataVarAbs,
HIDReportCount(1),
HIDReportSize(8),
HIDInputConstVarAbs,
HIDReportCount(6),
HIDReportSize(8),
HIDLogicalMinimum(0),
HIDLogicalMaximum(255),
HIDUsagePageKeyboard,
HIDUsageMinimum(0),
HIDUsageMaximum(255),
HIDInputDataAryAbs,
HIDCollectionEnd,
HIDUsagePageGenericDesktop,
HIDUsageDesktopMouse,
HIDCollectionApplication,
HIDUsageDesktopPointer,
HIDCollectionPhysical,
HIDReportID(1),
HIDUsagePageButton,
HIDUsageMinimum(1),
HIDUsageMaximum(5),
HIDLogicalMinimum(0),
HIDLogicalMaximum(1),
HIDReportCount(5),
HIDReportSize(1),
HIDInputDataVarAbs,
HIDReportCount(1),
HIDReportSize(3),
HIDInputConstVarAbs,
HIDUsagePageGenericDesktop,
HIDUsageDesktopX,
HIDUsageDesktopY,
HIDUsageDesktopWheel,
HIDLogicalMinimum(-127),
HIDLogicalMaximum(127),
HIDReportSize(8),
HIDReportCount(3),
HIDInputDataVarRel,
HIDCollectionEnd,
HIDCollectionEnd,
HIDUsagePageConsumer,
HIDUsageConsumerControl,
HIDCollectionApplication,
HIDReportID(3),
HIDLogicalMinimum(0),
HIDLogicalMaximum(8191),
HIDUsageMinimum(0),
HIDUsageMaximum(0x1FFF),
HIDReportSize(16),
HIDReportCount(1),
HIDInputDataAryAbs,
HIDCollectionEnd}),
},
}

Some files were not shown because too many files have changed in this diff Show More