Skip to content

usb: support the USB peripheral in ATmegaxxU2 and ATmegaxxU4 MCUs - #603

Open
teknoman117 wants to merge 6 commits into
buserror:masterfrom
teknoman117:usb-32u2-32u4
Open

teknoman117 wants to merge 6 commits into
buserror:masterfrom
teknoman117:usb-32u2-32u4

Conversation

@teknoman117

@teknoman117 teknoman117 commented Nov 8, 2026 •

Copy link
Copy Markdown

Implement support for the usb cores in the ATmegaxxU2 and ATmegaxxU4 MCUs. Also provide automated tests for USB cores.

In combination with #604 I am able to boot the Arduino Micro and Arduino Leonardo's USB bootloader under simavr.

Closes #467.

- Per-core endpoint geometry: endpoint count, DPRAM size, maximum size
  and bank count per endpoint. CFGOK is only set when the configuration
  fits. Cores that leave these at zero keep the old at90usb162 limits.
- 16 bit byte count (UEBCHX) for the 256 byte endpoints of the xxU4.
- VBUS pad: AVR_IOCTL_USB_VBUS drives USBSTA.VBUS and VBUSTI when
  OTGPADE is set, a part with a VBUS pad only attaches with VBUS present.
- USB_GEN and USB_COM are level triggered: enabling an interrupt whose
  flag is already set fires it.
- Start of frame every 1ms after a bus reset, with frame number.
- UERST, EPEN, RSTDT, UENUM and read-modify-write of STALLRQ/STALLRQC
  behave as in the datasheets, UDINT/USBINT bits are clear only.
- Host ioctls NAK while detached or on unconfigured endpoints, refuse
  endpoints the part does not have, truncate oversized packets and
  report STALL on the endpoint that was accessed.
@teknoman117 teknoman117 changed the title Implement support for ATmegaxxU2 and ATmegaxxU4 USB cores usb: support the USB peripheral in ATmegaxxU2 and ATmegaxxU4 MCUs Nov 8, 2026
@gatk555

gatk555 commented Nov 8, 2026

Copy link
Copy Markdown
Collaborator

I pulled and built this and everything looks good. But some explanation is needed: what does it actually do and what is the interaction with with previous USB support?

I never touched the old stuff apart from making it still build, but my belief is that it depended on a long-obsolete Linux kernel module to do something. My guess is that was rather like the simduino stuff and it would appear to the OS as an actual peripheral that could be seen and used. Looking quickly through your commits, I see nothing like that.

Also, what might user-level documentation look like? I have been trying to improve that with Intro.html.

Thanks, G.

@teknoman117

Copy link
Copy Markdown
Author

You are correct about vhci_usb not building on modern kernels. I'm currently working on a replacement built on USB/IP, which is in upstream Linux and will also allow it to work on Windows.

As for how I actually tested all this, simavr separates the implementation of the usb peripheral from host integration. sim/avr_usb.c models the peripheral and offers AVR_IOCTL_USB_* calls to whatever plays the USB host. make run_tests has tests/test_usb_host.h acting as the host by calling the IOCTLs directly. I made a small test harness that does the same, but acts as avrdude. With #604 I was able to run the Arduino Micro and Leonardo bootloader (Caterina). It enumerates the device, sends AVR109 commands to erase the chip, writes the unmodified ChibiOS Arduino Micro test application one page at a time and read it back, then exits the bootloader. Caterina's SPM code does the flash writes through simavr's existing self-programming support, and the application then starts and enumerates.

Changes to the model. References are to the ATmega16U4/32U4 datasheet (Atmel-7766J, 04/2016), written U4 below, and the ATmega8U2/16U2/32U2 datasheet (7799E, 09/2012), written U2.

Endpoint memory: UECFG0X, UECFG1X, UESTA0X.CFGOK

  • Each core now describes its endpoints, and setting ALLOC only sets CFGOK if EPSIZE/EPBK fit that endpoint and the total fits the DPRAM (UESTA0X, U4 p287; "CFGOK will not be set if the Endpoint size parameter is bigger than the DPRAM size", U4 §22.6 p271, U2 §21.6 p199). Before, every configuration was accepted.
    • xxU4: 832 bytes. EP0 up to 64; EP1 up to 256, one or two banks; EP2–6 up to 64, one or two banks (U4 §21.1 p256).
    • xxU2: 176 bytes. EP0–2 up to 64, single bank; EP3–4 up to 64, double bank (U2 §20.1 p185, §21.1 p197).
  • Memory isn't actually moved when endpoints are freed and re-allocated, so the overlapping "conflict" case in U4 §21.9 (p264), where CFGOK is still set but data is lost, isn't reproduced.

Endpoint registers

  • UEBCHX (BYCT10:8) for the 16-bit byte count of 256-byte endpoints (U4 p291). The xxU2 only have UEBCLX (U2 §21.18.18 p221).
  • UENUM is 3 bits wide (EPNUM2:0, U4 p285, U2 §21.18.8 p214). Selecting an endpoint the chip doesn't have now logs a warning instead of indexing past the endpoint array.
  • UERST now resets the selected endpoints. The banks are emptied and UEINTX, UESTA0X and UESTA1X return to their reset values, while the configuration and data toggle are kept (U4 §22.3 p270, U2 §21.3 p197).
  • UECONX:
    • STALLRQC is "cleared by hardware immediately after the set" (U4 p286, U2 §21.18.10 p214), so UECONX |= (1 << STALLRQC) now clears a pending STALLRQ.
    • RSTDT resets the data toggle.
    • Clearing EPEN disables and resets the endpoint.
  • UESTA1X is read-only ("Can not be set or cleared by software", U4 p289, U2 §21.18.14 p218).
  • UEINT (EPINT6:0, or EPINT4:0 on the xxU2) is computed from UEINTX & UEIENX, and goes away when the source is served (U4 p292, U2 §21.18.19 p222).

Interrupts

  • Writes to UDINT, USBINT and UEINTX can only clear bits ("Shall be cleared by software. Setting by software has no effect.", U4 p282 and p289–290, U2 §21.18.2 p210).
  • USB_GEN and USB_COM are level triggered. The interrupt diagrams AND each flag with its enable and OR the results into the vector (U4 Figures 21-8 to 21-10, p261–262; U2 Figures 20-8 to 20-10, p190–191). So enabling an interrupt whose flag is already set now fires it, and so does leaving a flag set across RETI. Before, an interrupt only fired at the moment its flag was set.

Start of frame

  • After a bus reset, SOFI is raised every 1 ms (U4 p282, U2 §21.18.2 p210), and UDFNUMH/UDFNUML count the 11-bit frame number (U4 p284, U2 §21.18.5–6 p213). The generator was already in avr_usb.c but disabled with if (0). Firmware that does work from SOF needs it; ChibiOS's serial-over-USB driver, for example, flushes partial packets from there.

VBUS, xxU4 only

  • USBCON.OTGPADE enables the VBUS pad and VBUSTE its interrupt. USBSTA.VBUS shows the VBUS level (ID reads as 1), and USBINT.VBUSTI is set on every transition (U4 §21.13.1 p267–268, §21.11 p265). AVR_IOCTL_USB_VBUS drives the level. It was defined before but never handled, so firmware that waits for VBUS never attached. vhci_usb.c already sends it when the port is powered.
  • The xxU2 have no VBUS pad: their USBCON only has USBE and FRZCLK (U2 §20.10.1 p195). They still attach as soon as DETACH is cleared.

Controller and PLL

  • Clearing USBCON.USBE resets the controller ("Clear to disable and reset the USB controller", U4 p267, U2 §20.10.1 p195). The model now detaches and clears all endpoints when that happens.
  • PLLCSR.PLOCK is read-only and follows PLLE ("To clear PLOCK, clear PLLE", U4 §6.11.5 p41, U2 §8.11.6 p40–41). Before, a read-modify-write that cleared PLLE left PLOCK set.

Host-side IOCTLs

  • They return NAK while the device is detached or the endpoint isn't configured, and -1 for endpoints the chip doesn't have.
  • STALL is reported on the endpoint that was accessed (it was always EP0 before).
  • OUT data larger than the endpoint is cut short and flagged as an overflow to the firmware.

Not modelled

  • Suspend/wake-up, OTG/host mode, actual use of the second bank, data toggle checking, FNCERR and isochronous CRC errors.

Cores

  • New: atmega8u2, atmega16u2, atmega16u4, at90usb82. The xxU2 and xxU4 cores now share sim_megaxu2.h/sim_megaxu4.h templates, as the x4 family already does, since the parts of a family only differ in memory sizes.

For docs, I've added a commit with a USB section in Intro.html, after "Other peripherals". It covers which parts have USB, that the program using libsimavr acts as the host, the IOCTLs with a short enumeration example, what isn't simulated, and where vhci stands. I've also documented each IOCTL in avr_usb.h, since the return values and one quirk were only discoverable by reading avr_usb.c. The quirk: a bulk IN endpoint with no data returns an empty read instead of NAK.

@teknoman117

teknoman117 commented Nov 9, 2026 •

Copy link
Copy Markdown
Author

Here's an (admittedly completely opus 5.5 generated) USB/IP replacement to vhci_usb: https://github.com/teknoman117/simavr/tree/usbip.

To Use:

# Terminal 1
cd /path/to/simavr/examples/board_usbip
make
./obj-x86_64-pc-linux-gnu/simusbip.elf -vv

# Terminal 2
sudo modprobe vhci-hcd
usbip list -r 127.0.0.1
sudo usbip attach -r 127.0.0.1 -b 1-1
sudo picocom /dev/ttyACM0

@gatk555

gatk555 commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator

Thank you for the detailed explanation, but I did not read it all. Reading more than a few lines of LLM prose feels to me like sitting through a corporate presentation. (Apologies in advance if it was not LLM output!)

I have done some digging in code and datasheet and turned up a few points,

Not part of the PR, but extra_board_usb/simusb.c seems incomplete: there is nothing to connect the simulation and the kernel module. I think the directory can be deleted.

"I made a small test harness that does the same, but acts as avrdude. ..." That sounds like something that would be good to have in the examples tree. So would a program showing USB/IP integration.

"USB_GEN and USB_COM are level triggered." Plausible, but I do not see it in the datasheet, which just seems vague, and below the usual standard of AVR documentation. If correct, the "sticky" bit should be set in the two Simavr avr_int_vector_t structs, and there should be calls to avr_clear_interrupt() in avr_usb.c.

b38a9a8 makes atmegausb162.c and atmega32u2.c almost identical. Confusingly, atmegausb162.c already defines a core called "at90usb162", not "atmegausb162", but Microchip considers these to be three different parts. A search turns up "AVR 530: Migrating from AT90USB162/82 to ATmega16U2/8U2" which suggests the identification is wrong, and lists some relevant differences in peripherals.

There are some small changes that I would like so see berfore or during merge: squash c120683 into the previous commit, but also change it to a simple dependency and change the last line of the avr-gcc command in Makefile.common to:

   ${filter-out %.h, ${^}} -o ${@}

I recently met a very similar problem and that seems a general solution. Also, I think it is not necessary to test each memory variant and would remove some firmware files and comment-out (but keep) their table entries. The tests directory is already looking cluttered.

On documentation: I think you added too much to Intro.html, but it would be useful as a separate USB guide. It seems USB is very like the other communication peripherals: some emulated register bits and a way for the host program to send and receive.

@teknoman117

teknoman117 commented Nov 11, 2026 •

Copy link
Copy Markdown
Author

I'll start working on those changes.

(Apologies in advance if it was not LLM output!)

No, you had it right. Apologies. Everything after "Changes to the model" was LLM generated.

Not part of the PR, but extra_board_usb/simusb.c seems incomplete: there is nothing to connect the simulation and the kernel module. I think the directory can be deleted.

The extra_board_usb does the vhci driver binding at https://github.com/buserror/simavr/blob/master/examples/extra_board_usb/simusb.c#L139. But, as you said, the vhci_usb module is not maintained by anyone anymore. Unless you feel otherwise, USB/IP seems like a good path forward, and it has the benefit of being cross platform. Well, at least Linux and Windows, looks like Apple is being picky about signing the macOS version of the driver.

Do you want me to merge my USB/IP tree into this PR as well? I will also bundle the avrdude test harness I made. I was trying to keep the overall PR length somewhat under control. I'm already well over what I'd accept at work 😓.

If so, would you like me to just nuke the extra_board_usb folder and the vhci folders and replace it with a "board_arduino_leonardo" and put the USB/IP integration code somewhere near where the existing vhci folder is?

In general I'm trying to ensure the CI/CD pipeline for ChibiOS's AVR port is able to cover the USB peripheral for supported microcontrollers. I only have hardware examples of the ATmega32u4 in the form of an old Arduino Micro floating around my parts bin.

Testing has admittedly only been end-to-end. Basically, does code I know works on the real hardware run correctly on the emulated hardware - test images built on the ChibiOS AVR port, the ATmega32u4 Arduino Bootloader (Catarina), and The ATmega16u2 USB-to-UART adapter flash dump from the Arduino Uno Rev. 3

Move the xxU2 and xxU4 cores to sim_megaxu2.h and sim_megaxu4.h
templates; within each family the cores simavr builds only differ in
memory sizes:

- at90usb82, at90usb162, atmega8u2, atmega16u2, atmega32u2: 5 endpoints,
  176 bytes of DPRAM.
- atmega16u4, atmega32u4: 7 endpoints, 832 bytes of DPRAM, 256 byte
  EP1, VBUS pad.

The ATmega8U2/16U2 add WDTCKD and ACMUX to the AT90USB82/162 they
replace (Atmel AVR530); simavr models neither, which the template notes.

The at90usb162 now honours PRR0.PRSPI. avr/iousb82.h lacks the
signature bytes, sim_usb82.c provides them.
The %.axf rule compiles all its prerequisites, so a firmware could not depend on a header without the header being handed to avr-gcc. Filter headers out, so that they can be listed as dependencies.
A bare metal device firmware probes endpoint configurations, enumerates
and echoes bulk data. The host side checks attach/VBUS handling, control
transfers, STALL, the CFGOK results and the echo, plus each core's
memory sizes and signature.

The parts of a family only differ in memory sizes, so firmware is built
for the at90usb162, atmega32u2 and atmega32u4 only. The table entries
for the other parts are kept, commented out.
doc/USB.html describes which parts are simulated, how the program using
libsimavr acts as the host through the AVR_IOCTL_USB_* calls, with a
short enumeration fragment, what is not simulated, and the state of the
usb-vhci bridge in examples/extra_board_usb. Intro.html gets a short
entry pointing to it.

avr_usb.h documents each IOCTL's parameter, return values and the
bulk IN zero length quirk, which was only discoverable by reading
avr_usb.c.
The two vectors have no flag of their own, and the UDINT, USBINT and
UEINTX flags are only ever cleared by software. They are the second
type in the datasheet's "Reset and Interrupt Handling" section (U4
§4.8, U2 §6.8): they trigger for as long as the condition is present.

Instead of raising the vectors again after every register write, they
are now declared raise_sticky, raised while an enabled flag is set and
cleared with avr_clear_sticky_interrupt() when none is.
@teknoman117

teknoman117 commented Nov 11, 2026 •

Copy link
Copy Markdown
Author

Also, just for clarification, how would you like me to include the Arduino Micro / Leonardo + avrdude emulation harness and/or the atmega16u2 usb-to-serial harness? As an example? As a test? Both?

If any, how would you like me to bundle the firmware file for those? I see simduino has a hex file for the bootloader already. I believe the Caterina bootloader for 32u4 is the MIT license (inherits LUFA's license) and the atmega16u2 usb-to-serial is definitely MIT license.

@gatk555

gatk555 commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

Your comment about PR size makes sense and any example code would probably not work without #604. It sounds as if you are still working on the application, so it might be a mistake to take an early copy. Assuming it is accessible, maybe just add a link with any documentation.

Thanks, G.

@teknoman117

teknoman117 commented Nov 11, 2026 •

Copy link
Copy Markdown
Author

@gatk555 Here's the tree for the USB/IP work: https://github.com/teknoman117/simavr/tree/usbip. Removes the VHCI interface, replaces with USB/IP.

See:

Example

$ ./obj-x86_64-pc-linux-gnu/simduino_leonardo.elf
Loaded 4058 bytes of Flash data at 0x7000
Arduino Leonardo, USB/IP server on 127.0.0.1 port 3240. To attach it:
  Linux:   sudo modprobe vhci-hcd
           sudo usbip attach -r 127.0.0.1 -b 1-1
  Windows: usbip attach -r 127.0.0.1 -b 1-1 (usbip-win2, from an administrator prompt)

$ usbip list -r 127.0.0.1
Exportable USB devices
======================
 - 127.0.0.1
        1-1: Arduino SA : Leonardo Bootloader (2341:0036)
           : /sys/devices/simavr/usb1/1-1
           : Communications / unknown subclass / unknown protocol (02/00/00)
           :  0 - Communications / Abstract (modem) / AT-commands (v.25ter) (02/02/01)
           :  1 - CDC Data / Unused / unknown protocol (0a/00/00)

$ sudo usbip attach -r 127.0.0.1 -b 1-1
$ lsusb | grep 2341
Bus 005 Device 002: ID 2341:0036 Arduino SA Leonardo Bootloader
$ usbip port
Imported USB devices
====================
Port 00: <Port in Use> at Full Speed(12Mbps)
       Arduino SA : Leonardo Bootloader (2341:0036)
       5-1 -> unknown host, remote port and remote busid
           -> remote bus/dev 001/001

$ avrdude -c avr109 -p m32u4 -P /dev/ttyACM0 -U flash:w:sketch.hex
...
3670 bytes of flash verified
Avrdude done.  Thank you.

$ usbip list -r 127.0.0.1 | grep 2341
        1-1: Arduino SA : Leonardo (CDC ACM, HID) (2341:8036)
$ sudo usbip attach -r 127.0.0.1 -b 1-1
$ lsusb | grep 2341
Bus 005 Device 003: ID 2341:8036 Arduino SA Leonardo (CDC ACM, HID)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect maximum number of USB endpoints

2 participants