Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
Pic32mxUsbCdcDevice.cxx
Go to the documentation of this file.
1
36#include <fcntl.h>
37
39#include "system_config.h"
40#include "utils/Atomic.hxx"
41#include "freertos_drivers/pic32mx/int_pic32mx795/int_defines.h"
42
43extern "C"
44{
45#include "driver/usb/usbfs/drv_usbfs.h"
46#include "usb/usb_device.h"
47#include "usb/usb_device_cdc.h"
48
50extern const DRV_USBFS_INIT drvUSBFSInit;
52extern const USB_DEVICE_INIT usbDevInitData;
53} // extern "C"
54
55#include "os/os.h"
56
92class Pic32mxCdc : public Serial, private Atomic
93{
94public:
98 Pic32mxCdc(const char *name);
99
101 ~Pic32mxCdc();
102
104 inline void interrupt_handler()
105 {
106 DRV_USBFS_Tasks_ISR(drvUSBObject);
107 }
108
112 inline void device_tasks();
113
117 inline void event_hander_from_isr(USB_DEVICE_EVENT event, void *pData);
118
121 inline USB_DEVICE_CDC_EVENT_RESPONSE cdc_event_hander_from_isr(
122 USB_DEVICE_CDC_EVENT event, void *pData);
123
124private:
125 static constexpr unsigned USB_CDC_BUFFER_SIZE = 64;
126
127 static constexpr unsigned TX_BUFFER_SIZE = 3 * USB_CDC_BUFFER_SIZE;
128 static constexpr unsigned RX_DEVICEBUFFER_SIZE = 0;
129
131 void tx_char() override
132 {
133 }
135 void enable() override;
137 void disable() override;
139 void flush_buffers() override;
140
148 ssize_t read(File *file, void *buf, size_t count) override;
149
157 ssize_t write(File *file, const void *buf, size_t count) OVERRIDE;
158
165 bool select(File *file, int mode) override;
166
169 void start_read_atomic();
170
173 void start_write_atomic();
174
181
188
190 SYS_MODULE_OBJ drvUSBObject;
192 SYS_MODULE_OBJ usbDevObject0;
194 USB_DEVICE_HANDLE deviceHandle {USB_DEVICE_HANDLE_INVALID};
196 USB_CDC_LINE_CODING lineCodingData;
198 USB_DEVICE_CDC_TRANSFER_HANDLE readHandle;
200 USB_DEVICE_CDC_TRANSFER_HANDLE writeHandle;
201
202 // ==== These variables are protected by Atomic *this. ====
203
205 unsigned isEnabled : 1;
207 unsigned needAttach : 1;
209 unsigned isConfigured : 1;
217 unsigned currentReadBuffer : 1;
219 unsigned rxPending : 1;
222 unsigned nextReadBufferFull : 1;
225 unsigned txPendingBytes : 7;
226
229 unsigned driverErrors : 10;
230
233 unsigned irqVector : 5;
234
239
243
245};
246
247// Instantiates the driver.
248Pic32mxCdc usbCdc0("/dev/serUSB0");
249
250void *usb_device_task(void *)
251{
252 while (1)
253 {
254 usbCdc0.device_tasks();
255 vTaskDelay(configTICK_RATE_HZ / 20);
256 }
257}
258
259extern "C"
260{
261 void usb_interrupt()
262 {
263 usbCdc0.interrupt_handler();
264 }
265}
266
267Pic32mxCdc::Pic32mxCdc(const char *name)
268 : Serial(name, TX_BUFFER_SIZE, RX_DEVICEBUFFER_SIZE)
269 , isEnabled(0)
270 , needAttach(0)
271 , isConfigured(0)
272 , currentReadBuffer(0)
273 , rxPending(0)
274 , nextReadBufferFull(0)
275 , txPendingBytes(0)
276 , driverErrors(0)
277 , irqVector(usb_interrupt_vector_number)
278{
279 // Allocates receive buffers.
280 rxBuffers[0] = DeviceBuffer<uint8_t>::create(USB_CDC_BUFFER_SIZE);
281 rxBuffers[1] = DeviceBuffer<uint8_t>::create(USB_CDC_BUFFER_SIZE);
282
283 // Initialize Middleware
284 drvUSBObject = DRV_USBFS_Initialize(
285 DRV_USBFS_INDEX_0, (SYS_MODULE_INIT *)&drvUSBFSInit);
286
287 // Set priority of USB interrupt source. This has to be within kernel
288 // interrupt priority.
289 SYS_INT_VectorPrioritySet(INT_VECTOR_USB1, INT_PRIORITY_LEVEL1);
290
291 // Set Sub-priority of USB interrupt source
292 SYS_INT_VectorSubprioritySet(INT_VECTOR_USB1, INT_SUBPRIORITY_LEVEL0);
293
294 // Initialize the USB device layer
295 usbDevObject0 = USB_DEVICE_Initialize(
296 USB_DEVICE_INDEX_0, (SYS_MODULE_INIT *)&usbDevInitData);
297
298 // Background thread to execute connection tasks.
299 os_thread_t tid;
300 os_thread_create(&tid, "usb_tasks", 1, 512, &usb_device_task, nullptr);
301
302 // Setup default line coding parameters. The actual values are ignored, but
303 // they can be queried and set by the host.
304 lineCodingData.dwDTERate = 115200;
305 lineCodingData.bDataBits = 8;
306 lineCodingData.bParityType = 0;
307 lineCodingData.bCharFormat = 0;
308}
309
315
317{
318 AtomicHolder h(this);
319 isEnabled = true;
320 // Throws away all pending data.
323 {
326 }
327 if (!isConfigured)
328 {
329 return;
330 }
331 // Decides if we need to start a read.
332 if (!rxPending)
333 {
335 }
336}
338{
339 AtomicHolder h(this);
340 isEnabled = false;
341}
342
344{
345 AtomicHolder h(this);
346 rxBuffers[0]->flush();
347 rxBuffers[1]->flush();
348 nextReadBufferFull = false;
349 // Don't touch currentReadBuffer as there might be a pending RX. If the
350 // pending receive completes, we'll throw away the data when the next ::open
351 // happens.
352
353 // We don't throw away the tx buf because the application may have sent data
354 // with ::write but there is no way to wait for them for the tx buf to
355 // clear.
356}
357
359{
362 rxPending = 1;
363 auto *buf = next_rx_buffer();
364 DASSERT(!buf->pending());
365 buf->flush();
366 uint8_t *data;
367 volatile unsigned rem = buf->data_write_pointer(&data);
368 HASSERT(rem >= USB_CDC_BUFFER_SIZE);
369 auto ret = USB_DEVICE_CDC_Read(
370 USB_DEVICE_CDC_INDEX_0, &readHandle, data, USB_CDC_BUFFER_SIZE);
371 if (ret != USB_DEVICE_CDC_RESULT_OK)
372 {
373 ++driverErrors;
374 rxPending = false;
375 }
376}
377
378ssize_t Pic32mxCdc::read(File *file, void *buf, size_t count)
379{
380 uint8_t *data = static_cast<uint8_t *>(buf);
381 ssize_t result = 0;
382
383 while (count)
384 {
385 size_t bytes_read;
386 {
387 AtomicHolder h(this);
388 if ((current_rx_buffer()->pending() == 0) && nextReadBufferFull)
389 {
390 // Swap read buffers.
392 nextReadBufferFull = false;
393 }
394 // Starts new read request if needed.
396 {
398 }
399 bytes_read =
400 current_rx_buffer()->get(data, count < 64 ? count : 64);
401 }
402
403 if (bytes_read == 0)
404 {
405 /* no more data to receive */
406 if ((file->flags & O_NONBLOCK) || result > 0)
407 {
408 break;
409 }
410 else
411 {
412 // wait for data to come in. This calls select internally.
413 rxBuffers[0]->block_until_condition(file, true);
414 }
415 }
416
417 count -= bytes_read;
418 result += bytes_read;
419 data += bytes_read;
420 }
421 if (!result && (file->flags & O_NONBLOCK))
422 {
423 return -EAGAIN;
424 }
425
426 return result;
427}
428
430{
432 uint8_t *data;
433 // We use data_read_pointer to get a pointer to a consecutive block of
434 // bytes. This will automatically align us to 64-byte boundary every time
435 // the ring buffer rolls over. The result is that if the application
436 // suddenly starts sending a lot of data we'll end up batching everything
437 // into full urbs.
438 size_t len = txBuf->data_read_pointer(&data);
439 if (!len)
440 return;
441 USB_DEVICE_CDC_TRANSFER_FLAGS flag =
442 USB_DEVICE_CDC_TRANSFER_FLAGS_DATA_COMPLETE;
443 if (len > USB_CDC_BUFFER_SIZE ||
444 ((len == USB_CDC_BUFFER_SIZE) &&
445 (txBuf->pending() > USB_CDC_BUFFER_SIZE)))
446 {
447 len = USB_CDC_BUFFER_SIZE;
448 flag = USB_DEVICE_CDC_TRANSFER_FLAGS_MORE_DATA_PENDING;
449 }
450 txPendingBytes = len;
451 auto ret = USB_DEVICE_CDC_Write(
452 USB_DEVICE_CDC_INDEX_0, &writeHandle, data, len, flag);
453 if (ret != USB_DEVICE_CDC_RESULT_OK)
454 {
455 ++driverErrors;
456 txPendingBytes = 0;
457 }
458}
459
460ssize_t Pic32mxCdc::write(File *file, const void *buf, size_t count)
461{
462 const unsigned char *data = (const unsigned char *)buf;
463 ssize_t result = 0;
464
465 while (count)
466 {
467 size_t bytes_written = 0;
468 if (isConfigured)
469 {
470 AtomicHolder h(this);
471 // We limit the amount of bytes we write with each iteration in
472 // order to limit the amount of time that interrupts are disabled
473 // and preserve our real-time performance.
474 bytes_written = txBuf->put(data, count < 64 ? count : 64);
475 if (txPendingBytes == 0)
476 {
478 }
479 }
480
481 if (bytes_written == 0)
482 {
483 /* no more data to receive */
484 if ((file->flags & O_NONBLOCK) || result > 0)
485 {
486 break;
487 }
488 else
489 {
490 // wait for space to be available. will call select inside.
491 txBuf->block_until_condition(file, false);
492 }
493 }
494 else
495 {
496 count -= bytes_written;
497 result += bytes_written;
498 data += bytes_written;
499 }
500 }
501
502 if (!result && (file->flags & O_NONBLOCK))
503 {
504 return -EAGAIN;
505 }
506
507 return result;
508}
509
510bool Pic32mxCdc::select(File *file, int mode)
511{
512 bool retval = false;
513
514 AtomicHolder l(this);
515 switch (mode)
516 {
517 case FREAD:
518 // This complicated expression ensures that we wake up a reader
519 // thread not only when there are actual bytes to read, but also
520 // when we need to swap reader buffers or when we need to start the
521 // initial read after usb is plugged into the host.
522 if ((current_rx_buffer()->pending() > 0) || nextReadBufferFull ||
524 {
525 retval = true;
526 }
527 else
528 {
530 }
531 break;
532 case FWRITE:
533 // We ensure to wake up a writer thread when the usb just got
534 // plugged in.
535 if (isConfigured && ((txBuf->space() > 0) || (txPendingBytes == 0)))
536 {
537 retval = true;
538 }
539 else
540 {
542 }
543 break;
544 default:
545 case 0:
546 /* we don't support any exceptions */
547 break;
548 }
549
550 return retval;
551}
552
553static void usb_device_event_handler(
554 USB_DEVICE_EVENT event, void *pData, uintptr_t context)
555{
556 usbCdc0.event_hander_from_isr(event, pData);
557}
558
560{
561 DRV_USBFS_Tasks(drvUSBObject);
562 USB_DEVICE_Tasks(usbDevObject0);
563
564 do
565 {
566 if (deviceHandle != USB_DEVICE_HANDLE_INVALID)
567 {
568 break;
569 }
571 USB_DEVICE_Open(USB_DEVICE_INDEX_0, DRV_IO_INTENT_READWRITE);
572 if (deviceHandle == USB_DEVICE_HANDLE_INVALID)
573 {
574 break;
575 }
576 USB_DEVICE_EventHandlerSet(deviceHandle, &usb_device_event_handler, 0);
577 needAttach = 1;
578 } while (0);
579
580 if (needAttach)
581 {
582 USB_DEVICE_Attach(deviceHandle);
583 needAttach = 0;
584 }
585}
586
587static USB_DEVICE_CDC_EVENT_RESPONSE cdc_device_event_handler(
588 USB_DEVICE_CDC_INDEX index, USB_DEVICE_CDC_EVENT event, void *pData,
589 uintptr_t userData)
590{
591 return usbCdc0.cdc_event_hander_from_isr(event, pData);
592}
593
594void Pic32mxCdc::event_hander_from_isr(USB_DEVICE_EVENT event, void *pData)
595{
596 switch (event)
597 {
598 case USB_DEVICE_EVENT_POWER_REMOVED:
599 USB_DEVICE_Detach(deviceHandle);
600 break;
601 case USB_DEVICE_EVENT_POWER_DETECTED:
602 needAttach = 1;
603 break;
604 case USB_DEVICE_EVENT_RESET:
605 case USB_DEVICE_EVENT_DECONFIGURED:
606 isConfigured = 0;
607 // Throws away all data pending to be written to the host.
608 txBuf->flush();
609 break;
610 case USB_DEVICE_EVENT_CONFIGURED:
611 {
612 auto v =
613 ((USB_DEVICE_EVENT_DATA_CONFIGURED *)pData)->configurationValue;
614 if (v != 1)
615 {
616 break;
617 }
618
619 // Registers the CDC Device application event handler.
620 USB_DEVICE_CDC_EventHandlerSet(0, &cdc_device_event_handler, 0);
621 isConfigured = 1;
622 // Wakes up readers and writers.
625 break;
626 }
627 case USB_DEVICE_EVENT_SUSPENDED:
628 break;
629 case USB_DEVICE_EVENT_RESUMED:
630 break;
631
632 default:
633 break;
634 }
635}
636
637USB_DEVICE_CDC_EVENT_RESPONSE Pic32mxCdc::cdc_event_hander_from_isr(
638 USB_DEVICE_CDC_EVENT event, void *pData)
639{
640 switch (event)
641 {
642 case USB_DEVICE_CDC_EVENT_GET_LINE_CODING:
643 // This means the host wants to know the current line coding. This
644 // is a control transfer request.
645
646 USB_DEVICE_ControlSend(
647 deviceHandle, &lineCodingData, sizeof(USB_CDC_LINE_CODING));
648
649 break;
650
651 case USB_DEVICE_CDC_EVENT_SET_LINE_CODING:
652 // This means the host wants to set the line coding. This is a
653 // control transfer request.
654
655 USB_DEVICE_ControlReceive(
656 deviceHandle, &lineCodingData, sizeof(USB_CDC_LINE_CODING));
657
658 break;
659
660 case USB_DEVICE_CDC_EVENT_SET_CONTROL_LINE_STATE:
661 // Note: here we could implement fake hardware flow control by
662 // looking at the DTR and DCD lines as given by the host. I don't
663 // think that's interesting as USB drivers always have builtin
664 // hardware flow control using kernel buffers.
665
666 // fall through
667 case USB_DEVICE_CDC_EVENT_CONTROL_TRANSFER_DATA_RECEIVED:
668 // acknowledge
669 USB_DEVICE_ControlStatus(
670 deviceHandle, USB_DEVICE_CONTROL_STATUS_OK);
671 break;
672 case USB_DEVICE_CDC_EVENT_CONTROL_TRANSFER_DATA_SENT:
673 break;
674 case USB_DEVICE_CDC_EVENT_SEND_BREAK:
675 break;
676 case USB_DEVICE_CDC_EVENT_READ_COMPLETE:
677 {
678 auto *params =
679 static_cast<USB_DEVICE_CDC_EVENT_DATA_READ_COMPLETE *>(pData);
680 next_rx_buffer()->advance(params->length);
681 rxPending = false;
682 nextReadBufferFull = true;
684 break;
685 }
686 case USB_DEVICE_CDC_EVENT_WRITE_COMPLETE:
687 {
688 auto *params =
689 static_cast<USB_DEVICE_CDC_EVENT_DATA_WRITE_COMPLETE *>(pData);
690 DASSERT(params->length == txPendingBytes);
691 txBuf->consume(params->length);
692 txPendingBytes = 0;
693 if (txBuf->pending())
694 {
696 }
698 break;
699 }
700 default:
701 break;
702 }
703}
const USB_DEVICE_INIT usbDevInitData
Initialization structure in flash.
const DRV_USBFS_INIT drvUSBFSInit
Initialization structure in flash.
See OSMutexLock in os/OS.hxx.
Definition Atomic.hxx:153
Lightweight locking class for protecting small critical sections.
Definition Atomic.hxx:130
void flush()
flush all the data out of the buffer and reset the buffer.
size_t advance(size_t items)
Add a number of items to the buffer by advancing the writeIndex.
size_t space()
Return the number of items for which space is available.
size_t pending()
Return the number of items in the queue.
size_t consume(size_t items)
Remove a number of items from the buffer by advancing the readIndex.
void select_insert()
Add client to list of clients needing woken.
void signal_condition_from_isr()
Signal the wakeup condition from an ISR context.
Implements a smart buffer specifically designed for character device drivers.
size_t get(T *buf, size_t items)
remove a number of items from the buffer.
size_t put(const T *buf, size_t items)
Insert a number of items to the buffer.
size_t data_read_pointer(T **buf)
Get a reference to the current location in the buffer for read.
void destroy()
Destroy an existing DeviceBuffer instance.
static DeviceBuffer * create(size_t size, size_t level=0)
Create a DeviceBuffer instance.
const char * name
device name
Definition Devtab.hxx:266
Device driver for PIC32MX USB virtual serial port.
unsigned currentReadBuffer
Index of the buffer for current (userspace) reads.
unsigned isConfigured
true when we have a configured host.
ssize_t write(File *file, const void *buf, size_t count) OVERRIDE
Write to a file or device.
DeviceBuffer< uint8_t > * next_rx_buffer()
unsigned isEnabled
True when we have open fds.
void start_write_atomic()
Issues a write to the USB driver stack.
unsigned nextReadBufferFull
true if the opposite read buffer (the one after current) also has data.
DeviceBuffer< uint8_t > * rxBuffers[2]
Receive buffers.
unsigned irqVector
unused.
SYS_MODULE_OBJ usbDevObject0
Module pointer for USB middleware device.
void flush_buffers() override
Called by the serial driver after the last fd gets closed.
Pic32mxCdc()
Default constructor.
USB_CDC_LINE_CODING lineCodingData
Holds last set line coding data that came from the host.
unsigned txPendingBytes
Zero if no writes are pending, otherwise the number of bytes we sent with the pending write to the US...
~Pic32mxCdc()
Destructor.
unsigned needAttach
true wehen we need to execute an attach command on the usb task.
USB_DEVICE_CDC_TRANSFER_HANDLE readHandle
Handle for the current pending read.
SYS_MODULE_OBJ drvUSBObject
Module pointer for USB core device.
unsigned driverErrors
Counts internal error conditions that should not occur but we are hoping we can recover from them.
void device_tasks()
Background thread for doing USB device driver tasks.
USB_DEVICE_HANDLE deviceHandle
Equivalent of an fd for the USB core driver.
void event_hander_from_isr(USB_DEVICE_EVENT event, void *pData)
This function is called from the interrupt context of the USB driver to notify us about various event...
void interrupt_handler()
Handle an interrupt.
bool select(File *file, int mode) override
Device select method.
void start_read_atomic()
Issues a read to the USB driver stack.
USB_DEVICE_CDC_TRANSFER_HANDLE writeHandle
Handle for the current pending write.
ssize_t read(File *file, void *buf, size_t count) override
Read from a file or device.
unsigned rxPending
true when we have an outstanding read request with the USB driver.
DeviceBuffer< uint8_t > * current_rx_buffer()
void disable() override
Called by the serial driver when the last fd gets closed.
void tx_char() override
Unused.
void enable() override
Called by the serial driver when the first fd gets opened.
USB_DEVICE_CDC_EVENT_RESPONSE cdc_event_hander_from_isr(USB_DEVICE_CDC_EVENT event, void *pData)
This function is called from the interrupt context of the USB driver to notify us about various event...
Private data for a serial device.
Definition Serial.hxx:46
DeviceBuffer< uint8_t > * txBuf
transmit buffer
Definition Serial.hxx:84
#define FWRITE
Workaround for missing header defines on some newlib versions.
Definition fcntl.h:58
#define FREAD
Workaround for missing header defines on some newlib versions.
Definition fcntl.h:53
#define OVERRIDE
Function attribute for virtual functions declaring that this funciton is overriding a funciton that s...
Definition macros.h:180
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
#define DASSERT(x)
Debug assertion facility.
Definition macros.h:159
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Removes default copy-constructor and assignment added by C++.
Definition macros.h:171
int os_thread_create(os_thread_t *thread, const char *name, int priority, size_t stack_size, void *(*start_routine)(void *), void *arg)
Create a thread.
Definition os.c:450
File information.
Definition Devtab.hxx:52
int flags
open flags
Definition Devtab.hxx:63