/** * 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 "pico/stdlib.h" #include "pico/usb_device.h" #include "configuration_manager.h" #include "configuration_types.h" // TODO: Duplicated from os_descriptors.h #define U16_HIGH(_u16) ((uint8_t) (((_u16) >> 8) & 0x00ff)) #define U16_LOW(_u16) ((uint8_t) ((_u16) & 0x00ff)) #define U16_TO_U8S_LE(_u16) U16_LOW(_u16), U16_HIGH(_u16) /** * We have multiple copies of the device configuration. This is the factory * default configuration, it is static data in the firmware. * We also potentially have a user configuration stored at the end of flash * memory. And an in RAM working configuration. * * The idea is that when the device boots, it tries to use the user config * from the end of flash. If that is not present, or is invalid, we use this * default config instead. * * If the user sends an updated configuration over the USB port, it is stored * in RAM as a working configuration, and is used (until we lose power). If * the user issues a save command the working configuration is written to flash * and becomes the new user configuration. */ static const default_configuration default_config = { .filters = { .filter = { FILTER_CONFIGURATION, sizeof(default_config.filters) }, .f1 = {PEAKING, 38, -19, 0.9}, .f2 = {LOWSHELF, 2900, 2, 0.7}, .f3 = {PEAKING, 430, 3, 3.5}, .f4 = {HIGHSHELF, 8400, 2, 0.7}, .f5 = {PEAKING, 4800, 3, 5} } }; /** * TODO: For now, assume we always get a complete configuration but maybe we * should handle merging configurations where, for example, only a new * filter_configuration_tlv was received. */ static uint8_t working_configuration[256]; static uint8_t result_buffer[256] = { U16_TO_U8S_LE(NOK), U16_TO_U8S_LE(4) }; static bool config_dirty = false; static uint16_t write_offset = 0; static uint16_t read_offset = 0; bool validate_filter_configuration(filter_configuration_tlv *filters) { if (filters->header.type != FILTER_CONFIGURATION) { printf("Error! Not a filter TLV (%x)..\n", filters->header.type); return false; } uint8_t *ptr = (uint8_t *)filters->header.value; const uint8_t *end = (uint8_t *)filters + filters->header.length; while ((ptr + 4) < end) { uint32_t type = *(uint32_t *)ptr; uint16_t remaining = (uint16_t)(end - ptr); printf("Found Filter Type %d (%p rem: %d)..\n", type, ptr, remaining); switch (type) { case LOWPASS: case HIGHPASS: case BANDPASSSKIRT: case BANDPASSPEAK: case NOTCH: case ALLPASS: { if (remaining < sizeof(filter2)) { printf("Error! Not enough data left for filter2 (%d)..\n", remaining); return false; } filter2 *args = (filter2 *)ptr; printf("Args: F0: %0.2f, Q: %0.2f\n", args->f0, args->Q); ptr += sizeof(filter2); break; } case PEAKING: case LOWSHELF: case HIGHSHELF: { if (remaining < sizeof(filter3)) { printf("Error! Not enough data left for filter3 (%d)..\n", remaining); return false; } filter3 *args = (filter3 *)ptr; printf("Args: F0: %0.2f, dbGain: %0.2f, Q: %0.2f\n", args->f0, args->dBgain, args->Q); ptr += sizeof(filter3); break; } default: printf("Unknown filter type\n"); return false; } } if (ptr != end) { printf("Error! Did not consume the whole TLV (%p != %p)..\n", ptr, end); return false; } printf("Config looks good..\n"); return true; } bool validate_configuration(tlv_header *config) { if (config->type != SET_CONFIGURATION) { printf("Unexpcected Config type: %d\n", config->type); return false; } uint8_t *ptr = (uint8_t *)config->value; const uint8_t *end = (uint8_t *)config + config->length; while (ptr < end) { tlv_header* tlv = (tlv_header*) ptr; printf("Found TLV type: %d\n", tlv->type); if (tlv->type == FILTER_CONFIGURATION) { if (!validate_filter_configuration((filter_configuration_tlv*) tlv)) { return false; } } ptr += tlv->length; } } void load_config() { // Try to load data from flash // If that is no good, use the default config } void save_config() { // Write data to flash } // This callback is called when the client sends a message to the device. // We implement a simple messaging protocol. The client sends us a message that // we consume here. All messages are constructed of TLV's (Type Length Value). // In some cases the Value may be a set of TLV's. However, each message has an // owning TLV, and its length determines the length of the transfer. // Once we have consumed the whole message, we validate it and populate the result // buffer with a TLV which we expect the client to read next. void config_out_packet(struct usb_endpoint *ep) { struct usb_buffer *buffer = usb_current_out_packet_buffer(ep); printf("config_out_packet %d\n", buffer->data_len); memcpy(&working_configuration[write_offset], buffer->data, buffer->data_len); write_offset += buffer->data_len; const uint16_t transfer_length = ((tlv_header*) working_configuration)->length; printf("config_length %d %d\n", transfer_length, write_offset); if (transfer_length >= write_offset) { // Command complete, fill the result buffer tlv_header* result = ((tlv_header*) result_buffer); write_offset = 0; if (validate_configuration((tlv_header*) working_configuration)) { result->type = OK; result->length = 4; } else { result->type = NOK; result->length = 4; } } usb_grow_transfer(ep->current_transfer, 1); usb_packet_done(ep); } // This callback is called when the client attempts to read data from the device. // The client should have previously written a command which will have populated the // result_buffer. The client should attempt to read 4 bytes (the Type and Length) // then attempt to read the rest of the data once the length is known. void config_in_packet(struct usb_endpoint *ep) { assert(ep->current_transfer); struct usb_buffer *buffer = usb_current_in_packet_buffer(ep); printf("config_in_packet %d\n", buffer->data_len); assert(buffer->data_max >= 3); const uint16_t transfer_length = ((tlv_header*) result_buffer)->length; const uint16_t packet_length = MIN(buffer->data_max, transfer_length - read_offset); memcpy(buffer->data, &result_buffer[read_offset], packet_length); buffer->data_len = packet_length; if (transfer_length >= read_offset) { // Done read_offset = 0; // If the client reads again, return an error tlv_header* result = ((tlv_header*) result_buffer); result->type = NOK; result->length = 4; } usb_grow_transfer(ep->current_transfer, 1); usb_packet_done(ep); }