usb: device: cdc_acm: Use ZLP to detect initial host read

Prevent ECHO on Linux by arming IN endpoint with ZLP when interface is
configured and making sure that actual payload is only sent after
initialization timeout. The ZLP is not visible to host side applications
because the applications are really accessing tty buffer and received
ZLP does not modify tty buffer in any way.

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
This commit is contained in:
Tomasz Moń 2023-11-02 08:43:26 +01:00 committed by Carles Cufí
commit 0127d000a2
2 changed files with 32 additions and 8 deletions

View file

@ -37,6 +37,13 @@ config CDC_ACM_BULK_EP_MPS
help
CDC ACM class bulk endpoints size
config CDC_ACM_TX_DELAY_MS
int
default 100
help
Time in milliseconds to wait before sending actual payload to host.
This is needed to prevent tty ECHO on Linux.
config CDC_ACM_IAD
bool "Force using Interface Association Descriptor"
default y

View file

@ -223,12 +223,27 @@ static void cdc_acm_write_cb(uint8_t ep, int size, void *priv)
k_work_submit_to_queue(&USB_WORK_Q, &dev_data->cb_work);
}
if (ring_buf_is_empty(dev_data->tx_ringbuf)) {
/* If size is 0, we want to schedule tx work even if ringbuf is empty to
* ensure that actual payload will not be sent before initialization
* timeout passes.
*/
if (ring_buf_is_empty(dev_data->tx_ringbuf) && size) {
LOG_DBG("tx_ringbuf is empty");
return;
}
k_work_schedule_for_queue(&USB_WORK_Q, &dev_data->tx_work, K_NO_WAIT);
/* If size is 0, it means that host started polling IN data because it
* has read the ZLP we armed when interface was configured. This ZLP is
* probably the best indication that host has started to read the data.
* Wait initialization timeout before sending actual payload to make it
* possible for application to disable ECHO. The echo is long known
* problem related to the fact that POSIX defaults to ECHO ON and thus
* every application that opens tty device (on Linux) will have ECHO
* enabled in the short window between open() and ioctl() that disables
* the echo (if application wishes to disable the echo).
*/
k_work_schedule_for_queue(&USB_WORK_Q, &dev_data->tx_work, size ?
K_NO_WAIT : K_MSEC(CONFIG_CDC_ACM_TX_DELAY_MS));
}
static void tx_work_handler(struct k_work *work)
@ -247,6 +262,10 @@ static void tx_work_handler(struct k_work *work)
return;
}
if (!dev_data->configured) {
return;
}
len = ring_buf_get_claim(dev_data->tx_ringbuf, &data,
CONFIG_USB_CDC_ACM_RINGBUF_SIZE);
@ -375,12 +394,10 @@ static void cdc_acm_do_cb(struct cdc_acm_dev_data_t *dev_data,
dev_data->configured = true;
cdc_acm_read_cb(cfg->endpoint[ACM_OUT_EP_IDX].ep_addr, 0,
dev_data);
}
if (!dev_data->tx_ready) {
dev_data->tx_ready = true;
/* if wait tx irq, invoke callback */
if (dev_data->cb != NULL && dev_data->tx_irq_ena) {
k_work_submit_to_queue(&USB_WORK_Q, &dev_data->cb_work);
/* Queue ZLP on IN endpoint so we know when host starts polling */
if (!dev_data->tx_ready) {
usb_transfer(cfg->endpoint[ACM_IN_EP_IDX].ep_addr, NULL, 0,
USB_TRANS_WRITE, cdc_acm_write_cb, dev_data);
}
}
break;