stm32/usbd_hid_interface: Rewrite USB HID interface code.

The previous version did not work on MCUs that only had USB device mode
(compared to OTG) because of the handling of NAK.  And this previous
handling of NAK had a race condition where a new packet could come in
before USBD_HID_SetNAK was called (since USBD_HID_ReceivePacket clears NAK
as part of its operation).  Furthermore, the double buffering of incoming
reports was not working, only one buffer could be used at a time.

This commit rewrites the HID interface code to have a single incoming
buffer, and only calls USBD_HID_ReceivePacket after the user has read the
incoming report (similar to how the VCP does its flow control).  As such,
USBD_HID_SetNAK and USBD_HID_ClearNAK are no longer needed.

API functionality from the user's point of view should be unchanged with
this commit.
pull/1/head
Damien George 2019-07-25 17:42:17 +10:00
parent b1129df478
commit fa07deda9f
4 changed files with 57 additions and 115 deletions

View File

@ -787,6 +787,11 @@ STATIC mp_obj_t pyb_usb_hid_recv(size_t n_args, const mp_obj_t *args, mp_map_t *
// receive the data
int ret = usbd_hid_rx(&self->usb_dev->usbd_hid_itf, vstr.len, (uint8_t*)vstr.buf, vals[1].u_int);
if (ret < 0) {
// error, just return 0/empty bytes
ret = 0;
}
// return the received data
if (o_ret != MP_OBJ_NULL) {
return mp_obj_new_int(ret); // number of bytes read into given buffer

View File

@ -1,113 +1,70 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* Taken from ST Cube library and heavily modified. See below for original
* copyright header.
* The MIT License (MIT)
*
* Copyright (c) 2019 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
******************************************************************************
* @file USB_Device/CDC_Standalone/Src/usbd_cdc_interface.c
* @author MCD Application Team
* @version V1.0.1
* @date 26-February-2014
* @brief Source file for USBD CDC interface
******************************************************************************
* @attention
*
* <h2><center>&copy; COPYRIGHT(c) 2014 STMicroelectronics</center></h2>
*
* Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.st.com/software_license_agreement_liberty_v2
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include "usbd_hid_interface.h"
#include "py/obj.h"
#include "irq.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "usb.h"
#if MICROPY_HW_USB_HID
uint8_t *usbd_hid_init(usbd_hid_state_t *hid_in) {
usbd_hid_itf_t *hid = (usbd_hid_itf_t*)hid_in;
hid->current_read_buffer = 0;
hid->last_read_len = 0;
hid->current_write_buffer = 0;
// Return the buffer to place the first USB OUT packet
return hid->buffer[hid->current_write_buffer];
hid->report_in_len = USBD_HID_REPORT_INVALID;
return &hid->report_in_buf[0]; // location to place first incoming report
}
// Data received over USB OUT endpoint is processed here.
// len: number of bytes received into the buffer we passed to USBD_HID_ReceivePacket
// Returns USBD_OK if all operations are OK else USBD_FAIL
int8_t usbd_hid_receive(usbd_hid_state_t *hid_in, size_t len) {
// Incoming report: save the length but don't schedule next report until user reads this one
usbd_hid_itf_t *hid = (usbd_hid_itf_t*)hid_in;
hid->current_write_buffer = !hid->current_write_buffer;
hid->last_read_len = len;
// initiate next USB packet transfer, to append to existing data in buffer
USBD_HID_ReceivePacket(&hid->base, hid->buffer[hid->current_write_buffer]);
// Set NAK to indicate we need to process read buffer
USBD_HID_SetNAK(&hid->base);
hid->report_in_len = len;
return USBD_OK;
}
// Returns number of ready rx buffers.
int usbd_hid_rx_num(usbd_hid_itf_t *hid) {
return hid->current_read_buffer != hid->current_write_buffer;
}
// timout in milliseconds.
// Returns number of bytes read from the device.
int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout) {
// Wait until we have buffer to read
uint32_t start = HAL_GetTick();
while (hid->current_read_buffer == hid->current_write_buffer) {
// Wraparound of tick is taken care of by 2's complement arithmetic.
if (HAL_GetTick() - start >= timeout) {
// timeout
return 0;
int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout_ms) {
// Wait for an incoming report
uint32_t t0 = mp_hal_ticks_ms();
while (hid->report_in_len == USBD_HID_REPORT_INVALID) {
if (mp_hal_ticks_ms() - t0 >= timeout_ms || query_irq() == IRQ_STATE_DISABLED) {
return -MP_ETIMEDOUT;
}
if (query_irq() == IRQ_STATE_DISABLED) {
// IRQs disabled so buffer will never be filled; return immediately
return 0;
}
__WFI(); // enter sleep mode, waiting for interrupt
MICROPY_EVENT_POLL_HOOK
}
// There is not enough space in buffer
if (len < hid->last_read_len) {
return 0;
}
// Copy bytes from report to user buffer
int n = MIN(len, hid->report_in_len);
memcpy(buf, &hid->report_in_buf[0], n);
// Copy bytes from device to user buffer
int read_len = hid->last_read_len;
memcpy(buf, hid->buffer[hid->current_read_buffer], read_len);
hid->current_read_buffer = !hid->current_read_buffer;
// Schedule to receive next incoming report
hid->report_in_len = USBD_HID_REPORT_INVALID;
USBD_HID_ReceivePacket(&hid->base, &hid->report_in_buf[0]);
// Clear NAK to indicate we are ready to read more data
USBD_HID_ClearNAK(&hid->base);
// Success, return number of bytes read
return read_len;
// Return number of bytes read into user buffer
return n;
}
#endif // MICROPY_HW_USB_HID

View File

@ -4,18 +4,22 @@
#ifndef MICROPY_INCLUDED_STM32_USBD_HID_INTERFACE_H
#define MICROPY_INCLUDED_STM32_USBD_HID_INTERFACE_H
#include <stdint.h>
#include "usbd_cdc_msc_hid.h"
#define USBD_HID_REPORT_INVALID ((size_t)-1)
typedef struct _usbd_hid_itf_t {
usbd_hid_state_t base; // state for the base HID layer
uint8_t buffer[2][HID_DATA_FS_MAX_PACKET_SIZE]; // pair of buffers to read individual packets into
int8_t current_read_buffer; // which buffer to read from
uint32_t last_read_len; // length of last read
int8_t current_write_buffer; // which buffer to write to
volatile size_t report_in_len;
uint8_t report_in_buf[HID_DATA_FS_MAX_PACKET_SIZE];
} usbd_hid_itf_t;
int usbd_hid_rx_num(usbd_hid_itf_t *hid);
int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout);
static inline int usbd_hid_rx_num(usbd_hid_itf_t *hid) {
return hid->report_in_len != USBD_HID_REPORT_INVALID;
}
int usbd_hid_rx(usbd_hid_itf_t *hid, size_t len, uint8_t *buf, uint32_t timeout_ms);
#endif // MICROPY_INCLUDED_STM32_USBD_HID_INTERFACE_H

View File

@ -1190,30 +1190,6 @@ uint8_t USBD_HID_SendReport(usbd_hid_state_t *hid, uint8_t *report, uint16_t len
return USBD_FAIL;
}
uint8_t USBD_HID_SetNAK(usbd_hid_state_t *hid) {
// get USBx object from pdev (needed for USBx_OUTEP macro below)
PCD_HandleTypeDef *hpcd = hid->usbd->pdev->pData;
USB_OTG_GlobalTypeDef *USBx = hpcd->Instance;
#if defined(STM32H7)
uint32_t USBx_BASE = (uint32_t)USBx;
#endif
// set NAK on HID OUT endpoint
USBx_OUTEP(HID_OUT_EP_WITH_CDC)->DOEPCTL |= USB_OTG_DOEPCTL_SNAK;
return USBD_OK;
}
uint8_t USBD_HID_ClearNAK(usbd_hid_state_t *hid) {
// get USBx object from pdev (needed for USBx_OUTEP macro below)
PCD_HandleTypeDef *hpcd = hid->usbd->pdev->pData;
USB_OTG_GlobalTypeDef *USBx = hpcd->Instance;
#if defined(STM32H7)
uint32_t USBx_BASE = (uint32_t)USBx;
#endif
// clear NAK on HID OUT endpoint
USBx_OUTEP(HID_OUT_EP_WITH_CDC)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK;
return USBD_OK;
}
#endif
// CDC/MSC/HID interface class callback structure