Contiki-NG
Loading...
Searching...
No Matches
usb-arch.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2021 Yago Fontoura do Rosario <yago.rosario@hotmail.com.br>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the copyright holder nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 * OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30/*---------------------------------------------------------------------------*/
31/**
32 * \addtogroup nrf
33 * @{
34 *
35 * \addtogroup nrf-dev Device drivers
36 * @{
37 *
38 * \addtogroup nrf-usb USB driver
39 * @{
40 *
41 * \file
42 * USB implementation for the nRF.
43 * \author
44 * Yago Fontoura do Rosario <yago.rosario@hotmail.com.br>
45 *
46 */
47/*---------------------------------------------------------------------------*/
48#include "contiki.h"
49/*---------------------------------------------------------------------------*/
50#if NRF_HAS_USB
51/*---------------------------------------------------------------------------*/
52#include "usb.h"
53#include "usb_descriptors.h"
54
55#include "nrfx.h"
56#include "nrfx_power.h"
57#include "nrf_ficr.h"
58#include "nrf_power.h"
59#include "nrf_gpio.h"
60
61#include "tusb_config.h"
62#include "tusb.h"
63
64#include "sys/process.h"
65
66PROCESS_NAME(usb_arch_process);
67/*---------------------------------------------------------------------------*/
68extern void tusb_hal_nrf_power_event(uint32_t event);
69/*---------------------------------------------------------------------------*/
70#define SERIAL_NUMBER_STRING_SIZE 12
71/*---------------------------------------------------------------------------*/
72static char serial[SERIAL_NUMBER_STRING_SIZE + 1];
73/*---------------------------------------------------------------------------*/
74void
75USBD_IRQHandler(void)
76{
78}
79/*---------------------------------------------------------------------------*/
80static void
81power_event_handler(nrfx_power_usb_evt_t event)
82{
83 tusb_hal_nrf_power_event((uint32_t)event);
84}
85/*---------------------------------------------------------------------------*/
86void
87usb_arch_init(void)
88{
89 /* On nRF52 series, FICR exposes DEVICEADDR -- the 48-bit BLE-style
90 * address. Use it so the USB iSerialNumber matches the value Nordic's
91 * open-bootloader and the old arch/platform/nrf52840 USB stack
92 * (app_usbd_serial_num_generate) report; the OR with 0xC000 mirrors
93 * how the SDK turns it into a static-random BLE address.
94 *
95 * On nRF5340/nRF54 series, FICR has no DEVICEADDR -- only an INFO
96 * struct with a DEVICEID. Fall back to that on those CPUs. The
97 * serial number won't match the nRF52 bootloader-vs-app trick (there
98 * is no comparable bootloader on those parts anyway), but it stays
99 * unique per device, which is all the descriptor needs. */
100#if defined(NRF52_SERIES) || defined(NRF52840_XXAA) \
101 || defined(NRF52833_XXAA) || defined(NRF52832_XXAA) \
102 || defined(NRF52820_XXAA) || defined(NRF52811_XXAA) \
103 || defined(NRF52810_XXAA) || defined(NRF52805_XXAA)
104 const uint16_t serial_num_high_bytes =
105 (uint16_t)NRF_FICR->DEVICEADDR[1] | 0xC000;
106 const uint32_t serial_num_low_bytes = NRF_FICR->DEVICEADDR[0];
107#else
108 const uint16_t serial_num_high_bytes =
109 (uint16_t)NRF_FICR->INFO.DEVICEID[1] | 0xC000;
110 const uint32_t serial_num_low_bytes = NRF_FICR->INFO.DEVICEID[0];
111#endif
112 const nrfx_power_config_t power_config = { 0 };
113 const nrfx_power_usbevt_config_t power_usbevt_config = {
114 .handler = power_event_handler
115 };
116
117 nrfx_power_init(&power_config);
118
119 nrfx_power_usbevt_init(&power_usbevt_config);
120
121 nrfx_power_usbevt_enable();
122
123 // Set up descriptor
124 snprintf(serial,
125 SERIAL_NUMBER_STRING_SIZE + 1,
126 "%04"PRIX16"%08"PRIX32,
127 serial_num_high_bytes,
128 serial_num_low_bytes);
129
131
132 nrfx_power_usb_state_t usb_reg = nrfx_power_usbstatus_get();
133 if(usb_reg == NRFX_POWER_USB_STATE_CONNECTED) {
134 tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_DETECTED);
135 } else if(usb_reg == NRFX_POWER_USB_STATE_READY) {
136 tusb_hal_nrf_power_event(NRFX_POWER_USB_EVT_READY);
137 }
138}
139/*---------------------------------------------------------------------------*/
140#if CFG_TUD_DFU_RUNTIME
141/*
142 * Standard USB DFU runtime detach handler. Set the Nordic open-bootloader
143 * retention pattern in GPREGRET so the bootloader stays in DFU mode after
144 * the system reset, and reboot. Mirrors the behavior of the old nrf52840
145 * platform's app_usbd_nrf_dfu_trigger-based handler in
146 * arch/cpu/nrf52840/usb/usb-dfu-trigger.c.
147 */
148#define BOOTLOADER_DFU_GPREGRET_MAGIC 0xB0u
149#define BOOTLOADER_DFU_START_BIT 0x01u
150#define BOOTLOADER_DFU_START (BOOTLOADER_DFU_GPREGRET_MAGIC | BOOTLOADER_DFU_START_BIT)
151
152#ifndef BOARD_DFU_SELF_RESET_PIN
153/* nRF52840 Dongle (PCA10059) has P0.19 solder-bridged to the chip's
154 * RESET pin. Driving it low triggers a pin reset, which the dongle's
155 * open-bootloader treats as a request to enter DFU mode. Boards without
156 * this hardware can override BOARD_DFU_SELF_RESET_PIN to -1 to skip the
157 * pin-reset path. */
158#define BOARD_DFU_SELF_RESET_PIN NRF_GPIO_PIN_MAP(0, 19)
159#endif
160
161void
162tud_dfu_runtime_reboot_to_dfu_cb(void)
163{
164 /* Drive the self-reset GPIO low. On PCA10059 the GP pin is solder-
165 * bridged to the chip's nRESET line, so this causes a hardware pin
166 * reset within microseconds and the bootloader (which sees
167 * RESETREAS.RESETPIN set) enters DFU mode. Mirrors what the OLD
168 * arch/cpu/nrf52840/usb/usb-dfu-trigger.c did and what RIOT-OS's
169 * boards/nrf52840dongle/reset.c does. The control transfer's USB
170 * ACK won't reach the host (the device disappears mid-transfer),
171 * but that's the expected behavior for bitWillDetach. */
172#if BOARD_DFU_SELF_RESET_PIN >= 0
173 nrf_gpio_cfg_output(BOARD_DFU_SELF_RESET_PIN);
174 nrf_gpio_pin_clear(BOARD_DFU_SELF_RESET_PIN);
175#endif
176
177 /* Belt-and-braces for boards without the GP-pin-to-RESET wiring:
178 * also set Nordic's GPREGRET DFU magic and trigger a soft reset.
179 * Won't run on PCA10059 because the pin-reset above is faster. */
180 nrf_power_gpregret_set(NRF_POWER, 0, BOOTLOADER_DFU_START);
181 NVIC_SystemReset();
182}
183#endif /* CFG_TUD_DFU_RUNTIME */
184/*---------------------------------------------------------------------------*/
185/*
186 * Nordic-vendor-specific DFU trigger interface, mirroring what the OLD
187 * arch/cpu/nrf52840/usb/usb-dfu-trigger.c exposed via Nordic SDK's
188 * app_usbd_nrf_dfu_trigger. Lets `nrfutil dfu usb-serial` and other
189 * Nordic-aware host tools reboot the dongle into Open DFU Bootloader
190 * without touching the physical RESET button.
191 *
192 * Wire format (from app_usbd_nrf_dfu_trigger_types.h):
193 * Interface class/subclass/protocol = 0xFF / 0x01 / 0x01
194 * Functional descriptor type = 0x21 (CS_FUNCTIONAL), 9 bytes
195 * Control requests on this interface:
196 * bRequest 0x00 DETACH (host->dev, no data, triggers reboot)
197 * bRequest 0x07 NORDIC_INFO (dev->host, 24 bytes of dfu_nordic_info)
198 * bRequest 0x08 SEM_VER (dev->host, ASCII version string)
199 */
200#include "device/usbd_pvt.h"
201
202#define NORDIC_DFU_TRIGGER_CLASS 0xFFu
203#define NORDIC_DFU_TRIGGER_SUBCLASS 0x01u
204#define NORDIC_DFU_TRIGGER_PROTOCOL 0x01u
205#define NORDIC_DFU_TRIGGER_CS_FUNCTIONAL 0x21u
206#define NORDIC_DFU_TRIGGER_REQ_DETACH 0x00u
207#define NORDIC_DFU_TRIGGER_REQ_NORDIC_INFO 0x07u
208#define NORDIC_DFU_TRIGGER_REQ_SEM_VER 0x08u
209
210struct nordic_dfu_info {
211 uint32_t wAddress;
212 uint32_t wFirmwareSize;
213 uint16_t wVersionMajor;
214 uint16_t wVersionMinor;
215 uint32_t wFirmwareID;
216 uint32_t wFlashSize;
217 uint32_t wFlashPageSize;
218} __attribute__((packed));
219
220static const struct nordic_dfu_info nordic_info = {
221 .wAddress = 0x1000u, /* App start (after Nordic MBR) */
222 .wFirmwareSize = 0u, /* Unknown at runtime; left zero */
223 .wVersionMajor = 1u,
224 .wVersionMinor = 0u,
225 .wFirmwareID = 0u,
226 .wFlashSize = 1024u * 1024u, /* nRF52840 has 1 MiB flash */
227 .wFlashPageSize = 4096u,
228};
229
230static const char nordic_sem_ver[] = "Contiki-NG DFU";
231
232static void
233nordic_dfu_trigger_init(void)
234{
235}
236/*---------------------------------------------------------------------------*/
237static void
238nordic_dfu_trigger_reset(uint8_t rhport)
239{
240 (void)rhport;
241}
242/*---------------------------------------------------------------------------*/
243static uint16_t
244nordic_dfu_trigger_open(uint8_t rhport,
245 tusb_desc_interface_t const *itf_desc,
246 uint16_t max_len)
247{
248 (void)rhport;
249 (void)max_len;
250
251 if(itf_desc->bInterfaceClass != NORDIC_DFU_TRIGGER_CLASS
252 || itf_desc->bInterfaceSubClass != NORDIC_DFU_TRIGGER_SUBCLASS
253 || itf_desc->bInterfaceProtocol != NORDIC_DFU_TRIGGER_PROTOCOL) {
254 return 0;
255 }
256
257 uint16_t drv_len = sizeof(tusb_desc_interface_t);
258 uint8_t const *p = tu_desc_next(itf_desc);
259
260 /* Optional Nordic functional descriptor (type 0x21, 9 bytes). */
261 if(tu_desc_type(p) == NORDIC_DFU_TRIGGER_CS_FUNCTIONAL) {
262 drv_len += tu_desc_len(p);
263 }
264
265 return drv_len;
266}
267/*---------------------------------------------------------------------------*/
268static bool
269nordic_dfu_trigger_control_xfer_cb(uint8_t rhport, uint8_t stage,
270 tusb_control_request_t const *request)
271{
272 if(stage != CONTROL_STAGE_SETUP) {
273 return true;
274 }
275
276 /* Standard SET_INTERFACE during enumeration. */
277 if(request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD
278 && request->bRequest == TUSB_REQ_SET_INTERFACE
279 && request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) {
280 tud_control_status(rhport, request);
281 return true;
282 }
283
284 /*
285 * Nordic's nrfutil and the Nordic SDK's own app_usbd_nrf_dfu_trigger
286 * dispatch class-type requests purely by direction+type, ignoring the
287 * recipient field. Tools in the wild (incl. the libusb-based scripts
288 * documented at https://hackjumpzero.ca/posts/2024/04/trigger-dfu-via-usb-on-nrf52840-dongle/)
289 * send DETACH with bmRequestType = OUT|CLASS|DEVICE (0x20), not the
290 * INTERFACE recipient (0x21) that a strict USB spec reader would
291 * expect. Accept any recipient here for compatibility.
292 */
293 if(request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR
294 && request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) {
295 return false;
296 }
297
298 switch(request->bRequest) {
299 case NORDIC_DFU_TRIGGER_REQ_DETACH:
300 /* Drive the dongle's self-reset GPIO (P0.19, solder-bridged to
301 * nRESET via SB2 on PCA10059) immediately, exactly like the OLD
302 * arch/cpu/nrf52840/usb/usb-dfu-trigger.c handler. Once the pin
303 * is driven low, the hardware reset fires within microseconds and
304 * the bootloader sees RESETREAS.RESETPIN set, which it treats as
305 * a request to enter DFU mode. No need to ACK the control transfer
306 * or do any further work -- the chip is gone. */
307 nrf_gpio_cfg_output(BOARD_DFU_SELF_RESET_PIN);
308 nrf_gpio_pin_clear(BOARD_DFU_SELF_RESET_PIN);
309 return true;
310 case NORDIC_DFU_TRIGGER_REQ_NORDIC_INFO:
311 return tud_control_xfer(rhport, request,
312 (void *)&nordic_info, sizeof(nordic_info));
313 case NORDIC_DFU_TRIGGER_REQ_SEM_VER:
314 return tud_control_xfer(rhport, request,
315 (void *)nordic_sem_ver,
316 sizeof(nordic_sem_ver) - 1);
317 default:
318 return false;
319 }
320}
321/*---------------------------------------------------------------------------*/
322static usbd_class_driver_t const nordic_dfu_trigger_driver = {
323#if CFG_TUSB_DEBUG >= 2
324 .name = "NORDIC-DFU",
325#endif
326 .init = nordic_dfu_trigger_init,
327 .reset = nordic_dfu_trigger_reset,
328 .open = nordic_dfu_trigger_open,
329 .control_xfer_cb = nordic_dfu_trigger_control_xfer_cb,
330 .xfer_cb = NULL,
331 .sof = NULL,
332};
333/*---------------------------------------------------------------------------*/
334/*
335 * Register the Nordic DFU trigger driver with TinyUSB. TinyUSB picks
336 * this up automatically via its weakly-defined application driver hook.
337 */
338usbd_class_driver_t const *
339usbd_app_driver_get_cb(uint8_t *driver_count)
340{
341 *driver_count = 1;
342 return &nordic_dfu_trigger_driver;
343}
344/*---------------------------------------------------------------------------*/
345/*
346 * Nordic's nrfutil (and the open-source Python triggers that mimic it)
347 * send the DFU DETACH control request with bmRequestType recipient
348 * field set to DEVICE rather than INTERFACE. The Nordic SDK's
349 * app_usbd_nrf_dfu_trigger never checks the recipient field, so it
350 * accepts the request regardless. TinyUSB, on the other hand, routes
351 * INTERFACE-recipient class requests to per-interface class drivers
352 * (which is what nordic_dfu_trigger_control_xfer_cb above handles) but
353 * routes DEVICE-recipient class requests through this global hook.
354 *
355 * Mirror Nordic's behavior by catching the DETACH request here too, so
356 * either recipient form works.
357 */
358bool
359tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage,
360 tusb_control_request_t const *request)
361{
362 if(stage != CONTROL_STAGE_SETUP) {
363 return true;
364 }
365
366 /* Only catch Nordic's vendor-class control requests at device level.
367 * Standard requests at device level (descriptors, configuration, etc.)
368 * are TinyUSB's responsibility. */
369 if(request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS
370 && request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR) {
371 return false;
372 }
373
374 switch(request->bRequest) {
375 case NORDIC_DFU_TRIGGER_REQ_DETACH:
376 /* Same inline pin-reset as the standard runtime callback above. */
377#if BOARD_DFU_SELF_RESET_PIN >= 0
378 nrf_gpio_cfg_output(BOARD_DFU_SELF_RESET_PIN);
379 nrf_gpio_pin_clear(BOARD_DFU_SELF_RESET_PIN);
380#endif
381 nrf_power_gpregret_set(NRF_POWER, 0, BOOTLOADER_DFU_START);
382 NVIC_SystemReset();
383 return true;
384 case NORDIC_DFU_TRIGGER_REQ_NORDIC_INFO:
385 return tud_control_xfer(rhport, request,
386 (void *)&nordic_info, sizeof(nordic_info));
387 case NORDIC_DFU_TRIGGER_REQ_SEM_VER:
388 return tud_control_xfer(rhport, request,
389 (void *)nordic_sem_ver,
390 sizeof(nordic_sem_ver) - 1);
391 default:
392 return false;
393 }
394}
395/*---------------------------------------------------------------------------*/
396#endif /* NRF_HAS_USB */
397/*---------------------------------------------------------------------------*/
398/**
399 * @}
400 * @}
401 * @}
402 */
void usb_interrupt_handler(void)
Handles the interrupt.
Definition usb.c:68
void usb_descriptor_set_serial(char *serial)
Set the serial.
void usb_arch_init(void)
Initialize the architecture specific USB driver.
#define PROCESS_NAME(name)
Declare the name of a process.
Definition process.h:288
Header file for the Contiki process interface.
USB descriptors header file for the nRF.