/* * 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 "descriptors.h" #define MAX_PACKET 64 static unsigned char buf[2][MAX_PACKET]; static bool tx_odd; static bool tx_data01; static volatile unsigned int nextaddr; static void *tx_data; static size_t tx_size; 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]); } /* TODO: Make this a shared thing across all USB endpoints */ /* puttx: place data in the current buffer descriptor */ static bool puttx(void *data, size_t size) { if (GET_BIT(usb_bdt[0][BDT_TX][tx_odd].desc, BD_OWN)) return false; /* TODO: Just stop bothering with const */ usb_bdt[0][BDT_TX][tx_odd].addr = data; usb_bdt[0][BDT_TX][tx_odd].desc = USB0_BD_INIT(size, tx_data01); tx_odd = !tx_odd; tx_data01 = !tx_data01; return true; } /* pushtx: attempt to push rest of the current transmission into a BD */ static bool pushtx(void) { size_t size = tx_size; if (tx_data == NULL) return false; if (size > MAX_PACKET) size = MAX_PACKET; if (!puttx(tx_data, size)) return false; tx_data = (char *)tx_data + size; tx_size -= size; if (tx_size == 0 && size < MAX_PACKET) tx_data = NULL; return true; } /* quetx: enqueue a transmission */ static void quetx(void *data, size_t size) { tx_data = data; tx_size = size; while (pushtx()) ; } /* 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_data = NULL; tx_odd = 0; tx_data01 = 0; } /* 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; puttx(NULL, 0); break; case 0x0009: /* SET CONFIGURATION */ usb_endpt1_enable(); puttx(NULL, 0); break; case 0x210a: /* SET IDLE */ puttx(NULL, 0); break; case 0x8106: /* GET INTERFACE */ case 0x8006: /* GET DESCRIPTOR */ switch (setup->value) { case 0x0100: /* DEVICE */ quetx(ds_dev, trunc(ARRLEN(ds_dev), setup->length)); return; case 0x0200: /* CONFIGURATION */ quetx(ds_conf, trunc(ARRLEN(ds_conf), setup->length)); return; case 0x0300: /* STRING 0 */ quetx(ds_lang, trunc(ARRLEN(ds_lang), setup->length)); return; case 0x0301: /* STRING 1 */ quetx(ds_str1, trunc(ARRLEN(ds_str1), setup->length)); return; case 0x2200: quetx(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: 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); 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; } }