zephyr/subsys/usb/class/netusb/function_eem.c
Loic Poulain d80ae8aeeb usb: netusb: Add CDC EEM network usb function
Add CDC Ethernet Emulation Model function driver. This usb network
function can be used for ethernet over USB. Ethernet packet are
encapsulated within EEM packets. An EEM pkt contains 2-byte header
and 4-byte sentinel (or crc).

Note that EEM packets can be split across USB packets but shall not
be split across USB transfers.

Signed-off-by: Loic Poulain <loic.poulain@linaro.org>
2018-04-06 06:50:34 -04:00

168 lines
3.5 KiB
C

/*
* Copyright (c) 2017 Linaro Ltd
*
* SPDX-License-Identifier: Apache-2.0
*/
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_USB_DEVICE_NETWORK_DEBUG_LEVEL
#define SYS_LOG_DOMAIN "function/eem"
#include <logging/sys_log.h>
#include <net_private.h>
#include <zephyr.h>
#include <usb_device.h>
#include <usb_common.h>
#include <net/net_pkt.h>
#include "netusb.h"
static u8_t tx_buf[NETUSB_MTU], rx_buf[NETUSB_MTU];
static inline u16_t eem_pkt_size(u16_t hdr)
{
if (hdr & BIT(15)) {
return hdr & 0x07ff;
} else {
return hdr & 0x3fff;
}
}
static int eem_send(struct net_pkt *pkt)
{
u8_t sentinel[4] = { 0xde, 0xad, 0xbe, 0xef };
u16_t *hdr = (u16_t *)&tx_buf[0];
struct net_buf *frag;
int ret, len, b_idx = 0;
/* With EEM, it's possible to send multiple ethernet packets in one
* transfer, we don't do that for now.
*/
len = net_pkt_ll_reserve(pkt) + net_pkt_get_len(pkt) + sizeof(sentinel);
/* Add EEM header */
*hdr = sys_cpu_to_le16(0x3FFF & len);
b_idx += sizeof(u16_t);
/* Add Ethernet Header */
memcpy(&tx_buf[b_idx], net_pkt_ll(pkt), net_pkt_ll_reserve(pkt));
b_idx += net_pkt_ll_reserve(pkt);
/* generate transfer buffer */
for (frag = pkt->frags; frag; frag = frag->frags) {
memcpy(&tx_buf[b_idx], frag->data, frag->len);
b_idx += frag->len;
}
/* Add crc-sentinel */
memcpy(&tx_buf[b_idx], sentinel, sizeof(sentinel));
b_idx += sizeof(sentinel);
/* transfer data to host */
ret = usb_transfer_sync(CONFIG_CDC_EEM_IN_EP_ADDR, tx_buf, b_idx,
USB_TRANS_WRITE);
if (ret != b_idx) {
SYS_LOG_ERR("Transfer failure");
return -EIO;
}
return 0;
}
static void eem_read_cb(u8_t ep, int size, void *priv)
{
u8_t *ptr = rx_buf;
do {
u16_t eem_hdr, eem_size;
struct net_pkt *pkt;
struct net_buf *frag;
if (size < sizeof(u16_t)) {
break;
}
eem_hdr = sys_le16_to_cpu(*(u16_t *)ptr);
eem_size = eem_pkt_size(eem_hdr);
if (eem_size + sizeof(u16_t) > size) {
/* eem pkt greater than transferred data */
SYS_LOG_ERR("pkt size error");
break;
}
size -= sizeof(u16_t);
ptr += sizeof(u16_t);
if (eem_hdr & BIT(15)) {
/* EEM Command, do nothing for now */
goto done;
}
pkt = net_pkt_get_reserve_rx(0, K_FOREVER);
if (!pkt) {
SYS_LOG_ERR("Unable to alloc pkt\n");
break;
}
frag = net_pkt_get_frag(pkt, K_FOREVER);
if (!frag) {
SYS_LOG_ERR("Unable to alloc fragment");
net_pkt_unref(pkt);
break;
}
net_pkt_frag_insert(pkt, frag);
/* copy payload and discard 32-bit sentinel */
if (!net_pkt_append_all(pkt, eem_size - 4, ptr, K_FOREVER)) {
SYS_LOG_ERR("Unable to append pkt\n");
net_pkt_unref(pkt);
break;
}
netusb_recv(pkt);
done:
size -= eem_size;
ptr += eem_size;
} while (size);
usb_transfer(CONFIG_CDC_EEM_OUT_EP_ADDR, rx_buf, sizeof(rx_buf),
USB_TRANS_READ, eem_read_cb, NULL);
}
static int eem_connect(bool connected)
{
if (connected) {
eem_read_cb(CONFIG_CDC_EEM_OUT_EP_ADDR, 0, NULL);
} else {
/* Cancel any transfer */
usb_cancel_transfer(CONFIG_CDC_EEM_OUT_EP_ADDR);
usb_cancel_transfer(CONFIG_CDC_EEM_IN_EP_ADDR);
}
return 0;
}
static struct usb_ep_cfg_data eem_ep_data[] = {
{
/* Use transfer API */
.ep_cb = usb_transfer_ep_callback,
.ep_addr = CONFIG_CDC_EEM_OUT_EP_ADDR
},
{
/* Use transfer API */
.ep_cb = usb_transfer_ep_callback,
.ep_addr = CONFIG_CDC_EEM_IN_EP_ADDR
},
};
struct netusb_function eem_function = {
.connect_media = eem_connect,
.class_handler = NULL,
.send_pkt = eem_send,
.num_ep = ARRAY_SIZE(eem_ep_data),
.ep = eem_ep_data,
};