// SPDX-FileCopyrightText: 2023-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later #include #include #include #include #include #include #include #include "scarlett2-firmware.h" // List of found firmware files struct found_firmware { char *fn; struct scarlett2_firmware_header *firmware; }; GHashTable *best_firmware = NULL; static int verify_sha256( const unsigned char *data, size_t length, const unsigned char *expected_hash ) { unsigned char computed_hash[SHA256_DIGEST_LENGTH]; SHA256(data, length, computed_hash); return memcmp(computed_hash, expected_hash, SHA256_DIGEST_LENGTH) == 0; } static struct scarlett2_firmware_file *read_header(FILE *file) { struct scarlett2_firmware_file *firmware = calloc( 1, sizeof(struct scarlett2_firmware_file) ); if (!firmware) { perror("Failed to allocate memory for firmware structure"); goto error; } size_t read_count = fread( &firmware->header, sizeof(struct scarlett2_firmware_header), 1, file ); if (read_count != 1) { if (feof(file)) fprintf(stderr, "Unexpected end of file\n"); else perror("Failed to read header"); goto error; } if (strncmp(firmware->header.magic, MAGIC_STRING, 8) != 0) { fprintf(stderr, "Invalid magic number\n"); goto error; } firmware->header.usb_vid = ntohs(firmware->header.usb_vid); firmware->header.usb_pid = ntohs(firmware->header.usb_pid); firmware->header.firmware_version = ntohl(firmware->header.firmware_version); firmware->header.firmware_length = ntohl(firmware->header.firmware_length); return firmware; error: free(firmware); return NULL; } struct scarlett2_firmware_header *scarlett2_read_firmware_header( const char *fn ) { FILE *file = fopen(fn, "rb"); if (!file) { perror("fopen"); fprintf(stderr, "Unable to open %s\n", fn); return NULL; } struct scarlett2_firmware_file *firmware = read_header(file); if (!firmware) { fprintf(stderr, "Error reading firmware header from %s\n", fn); return NULL; } fclose(file); return realloc(firmware, sizeof(struct scarlett2_firmware_header)); } struct scarlett2_firmware_file *scarlett2_read_firmware_file(const char *fn) { FILE *file = fopen(fn, "rb"); if (!file) { perror("fopen"); fprintf(stderr, "Unable to open %s\n", fn); return NULL; } struct scarlett2_firmware_file *firmware = read_header(file); if (!firmware) { fprintf(stderr, "Error reading firmware header from %s\n", fn); return NULL; } firmware->firmware_data = malloc(firmware->header.firmware_length); if (!firmware->firmware_data) { perror("Failed to allocate memory for firmware data"); goto error; } size_t read_count = fread( firmware->firmware_data, 1, firmware->header.firmware_length, file ); if (read_count != firmware->header.firmware_length) { if (feof(file)) fprintf(stderr, "Unexpected end of file\n"); else perror("Failed to read firmware data"); fprintf(stderr, "Error reading firmware data from %s\n", fn); goto error; } if (!verify_sha256( firmware->firmware_data, firmware->header.firmware_length, firmware->header.sha256 )) { fprintf(stderr, "Corrupt firmware (failed checksum) in %s\n", fn); goto error; } fclose(file); return firmware; error: scarlett2_free_firmware_file(firmware); fclose(file); return NULL; } void scarlett2_free_firmware_header(struct scarlett2_firmware_header *firmware) { if (firmware) free(firmware); } void scarlett2_free_firmware_file(struct scarlett2_firmware_file *firmware) { if (firmware) { free(firmware->firmware_data); free(firmware); } } static void free_found_firmware(gpointer data) { struct found_firmware *found = data; free(found->fn); scarlett2_free_firmware_header(found->firmware); free(found); } static void init_best_firmware(void) { if (best_firmware) return; best_firmware = g_hash_table_new_full( g_direct_hash, g_direct_equal, NULL, free_found_firmware ); } // Add a firmware file to the list of found firmware // files, if it's better than the one already found // for the same device. static void add_found_firmware( char *fn, struct scarlett2_firmware_header *firmware ) { gpointer key = GINT_TO_POINTER(firmware->usb_pid); struct found_firmware *found = g_hash_table_lookup(best_firmware, key); // already have a firmware file for this device? if (found) { // lower version number, ignore if (firmware->firmware_version <= found->firmware->firmware_version) { free(fn); scarlett2_free_firmware_header(firmware); return; } // higher version number, replace g_hash_table_remove(best_firmware, key); } found = malloc(sizeof(struct found_firmware)); if (!found) { perror("Failed to allocate memory for firmware structure"); return; } found->fn = fn; found->firmware = firmware; g_hash_table_insert(best_firmware, key, found); } // look for firmware files in the given directory static void enum_firmware_dir(const char *dir_name) { DIR *dir = opendir(dir_name); if (!dir) { if (errno == ENOENT) { fprintf(stderr, "Firmware directory %s does not exist\n", dir_name); return; } fprintf( stderr, "Error opening directory %s: %s\n", dir_name, strerror(errno) ); return; } struct dirent *entry; while ((entry = readdir(dir))) { char *full_fn; // check if the file is a .bin file if (strlen(entry->d_name) < 4 || strcmp(entry->d_name + strlen(entry->d_name) - 4, ".bin") != 0) continue; // check if the file is a regular file if (entry->d_type == DT_UNKNOWN) { struct stat st; full_fn = g_build_filename(dir_name, entry->d_name, NULL); if (stat(full_fn, &st) < 0) { perror("stat"); g_free(full_fn); continue; } if (!S_ISREG(st.st_mode)) { g_free(full_fn); continue; } } else if (entry->d_type != DT_REG) { continue; } else { full_fn = g_build_filename(dir_name, entry->d_name, NULL); } struct scarlett2_firmware_header *firmware = scarlett2_read_firmware_header(full_fn); if (!firmware) { fprintf(stderr, "Error reading firmware file %s\n", full_fn); g_free(full_fn); continue; } add_found_firmware(full_fn, firmware); } closedir(dir); } void scarlett2_enum_firmware(void) { init_best_firmware(); enum_firmware_dir(SCARLETT2_FIRMWARE_DIR); } uint32_t scarlett2_get_best_firmware_version(uint32_t pid) { struct found_firmware *found = g_hash_table_lookup( best_firmware, GINT_TO_POINTER(pid) ); if (!found) return 0; return found->firmware->firmware_version; } struct scarlett2_firmware_file *scarlett2_get_best_firmware(uint32_t pid) { struct found_firmware *found = g_hash_table_lookup( best_firmware, GINT_TO_POINTER(pid) ); if (!found) return NULL; return scarlett2_read_firmware_file(found->fn); }