/* * usb/endpt0.c -- USB endpoint 0 (control) handling * * Copyright (C) 2016-2017 Tomasz Kramkowski * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "bdt.h" #include "endpt0.h" #include "endpt1.h" #include "txhandler.h" #include "descriptors.h" #define MAX_PACKET 64 static unsigned char buf[2][MAX_PACKET]; static volatile unsigned int nextaddr; static struct tx_ctx tx; struct tok_setup { uint8_t reqtyp; uint8_t req; uint16_t value; uint16_t index; uint16_t length; }; /* read_setup: parse a SETUP token's data */ static void read_setup(struct tok_setup *setup, const void *_data) { const unsigned char *data = _data; setup->reqtyp = data[0]; setup->req = data[1]; setup->value = le16toh(&data[2]); setup->index = le16toh(&data[4]); setup->length = le16toh(&data[6]); } /* usb_endpt0_disable: Disable endpoint 0 (not valid) */ void usb_endpt0_disable(void) { } /* usb_endpt0_enable: Enable and reset endpoint 0 */ void usb_endpt0_enable(void) { usb_bdt[0][BDT_RX][BDT_EVEN].addr = buf[0]; usb_bdt[0][BDT_RX][BDT_EVEN].desc = USB0_BD_INIT(sizeof buf[0], 0); usb_bdt[0][BDT_RX][BDT_ODD].addr = buf[1]; usb_bdt[0][BDT_RX][BDT_ODD].desc = USB0_BD_INIT(sizeof buf[1], 0); usb_bdt[0][BDT_TX][BDT_EVEN].addr = NULL; usb_bdt[0][BDT_TX][BDT_EVEN].desc = 0; usb_bdt[0][BDT_TX][BDT_ODD].addr = NULL; usb_bdt[0][BDT_TX][BDT_ODD].desc = 0; USB0_ENDPT(0) = BV(ENDPT_EPRXEN) | BV(ENDPT_EPTXEN) | BV(ENDPT_EPHSHK); nextaddr = 0; tx = TX_CTX(usb_bdt[0][BDT_TX], MAX_PACKET); } /* trunc: truncate size_t to a limit TODO: MOVE THIS */ static inline size_t trunc(size_t val, size_t max) { if (val > max) return max; return val; } /* tok_setup: process a setup token */ static void tok_setup(struct tok_setup *setup) { switch (setup->reqtyp << 8 | setup->req) { case 0x0005: /* SET ADDRESS */ nextaddr = setup->value; tx_que(&tx, NULL, 0); break; case 0x0009: /* SET CONFIGURATION */ usb_endpt1_enable(); tx_que(&tx, NULL, 0); break; case 0x210a: /* SET IDLE */ tx_que(&tx, NULL, 0); break; case 0x8106: /* GET INTERFACE */ case 0x8006: /* GET DESCRIPTOR */ switch (setup->value) { case 0x0100: /* DEVICE */ tx_que(&tx, ds_dev, trunc(ARRLEN(ds_dev), setup->length)); return; case 0x0200: /* CONFIGURATION */ tx_que(&tx, ds_conf, trunc(ARRLEN(ds_conf), setup->length)); return; case 0x0300: /* STRING 0 */ tx_que(&tx, ds_lang, trunc(ARRLEN(ds_lang), setup->length)); return; case 0x0301: /* STRING 1 */ tx_que(&tx, ds_str1, trunc(ARRLEN(ds_str1), setup->length)); return; case 0x2200: tx_que(&tx, ds_hidrep, trunc(ARRLEN(ds_hidrep), setup->length)); return; } /* fall through */ default: /* We received an unexpected SETUP so we STALL the endpoint */ SET_BIT(USB0_ENDPT(0), ENDPT_EPSTALL); break; } } void usb_endpt0_token(uint8_t state) { volatile struct usb0_bd *bd; struct tok_setup setup; bd = &usb_bdt[0][GET_BIT(state, STAT_TX)][GET_BIT(state, STAT_ODD)]; switch (GET_BITS(bd->desc, BD_TOK_PID)) { case BD_TOK_PID_OUT: bd->desc = USB0_BD_INIT(sizeof buf[0], 1); break; case BD_TOK_PID_IN: tx_push(&tx); if (nextaddr) { USB0_ADDR = nextaddr; nextaddr = 0; } break; case BD_TOK_PID_SETUP: read_setup(&setup, bd->addr); bd->desc = USB0_BD_INIT(sizeof buf[0], 1); /* This is a bit of a dodgy thing to do */ usb_bdt[0][BDT_TX][BDT_EVEN].desc = 0; usb_bdt[0][BDT_TX][BDT_ODD].desc = 0; tx.data = NULL; tx.data01 = 1; tok_setup(&setup); /* SETUP sets CTL_TXSUSPENDTOKENBUSY to suspend TX */ USB0_CTL = BV(CTL_USBENSOFEN); break; } }