// SPDX-License-Identifier: BSD-3-Clause // // Copyright(c) 2018 Intel Corporation. All rights reserved. // // Author: Seppo Ingalsuo #include #include #include #include #include #include #include #include "kernel/abi.h" #include "kernel/header.h" #include "ipc/stream.h" #include "ipc/control.h" #define BUFFER_TAG_OFFSET 0 #define BUFFER_SIZE_OFFSET 1 #define BUFFER_ABI_OFFSET 2 /* Definitions for multiple IPCs */ enum sof_ipc_type { SOF_IPC_TYPE_3, SOF_IPC_TYPE_4, SOF_IPC_TYPE_COUNT }; struct ctl_data { /* the input file name */ char *input_file; /* the input file descriptor */ int in_fd; /* the output file descriptor */ int out_fd; /* cached buffer for input/output */ /* use uint32_t to ensure alignment: Warning on pointer arithmetic. */ uint32_t *buffer; int buffer_size; int ctrl_size; enum sof_ipc_type ipc_type; /* IPC type dependent magic number to use, expect */ uint32_t magic; /* flag for input/output format, binary or CSV */ bool binary; /* flag for input/output format, with or without abi header */ bool no_abi; /* component specific type / param_id, default 0 */ uint32_t type; /* set or get control value */ bool set; /* print ABI header */ bool print_abi_header; int print_abi_size; /* name of sound card device */ char *dev; char *cname; /* alsa ctl_elem pointers */ snd_ctl_t *ctl; snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *value; }; static void usage(char *name) { fprintf(stdout, "Usage:\t %s [-D ] [-c ]", name); fprintf(stdout, " [-s ]\n"); fprintf(stdout, "\t %s [-D ] [-n ]", name); fprintf(stdout, " [-s ]\n"); fprintf(stdout, "\t %s -g \n", name); fprintf(stdout, "\t %s -h\n", name); fprintf(stdout, "\nWhere:\n"); fprintf(stdout, " -D device name (default is hw:0)\n"); fprintf(stdout, " -c control name e.g."); fprintf(stdout, " numid=22,name=\\\"EQIIR1.0 EQIIR\\\"\"\n"); fprintf(stdout, " -n control id e.g. 22\n"); fprintf(stdout, " -i {3|4} selects the IPC type to use, defaults to 3 (IPC3)\n"); fprintf(stdout, " -g generates"); fprintf(stdout, " the current ABI header with given payload size\n"); fprintf(stdout, " -s set data using ASCII CSV input file\n"); fprintf(stdout, " -b set/get control in binary mode(e.g. for set, use binary input file, for get, dump out in hex format)\n"); fprintf(stdout, " -r no abi header for the input file, or not dumping abi header for get.\n"); fprintf(stdout, " -o specify the output file.\n"); fprintf(stdout, " -t specify the component specified type (IPC3 specific).\n"); fprintf(stdout, " -p specify the param_id of the data (IPC4 specific)."); fprintf(stdout, " Valid range: 0-255\n"); } static void header_init(struct ctl_data *ctl_data) { struct sof_abi_hdr *hdr = (struct sof_abi_hdr *)&ctl_data->buffer[BUFFER_ABI_OFFSET]; hdr->magic = ctl_data->magic; hdr->type = ctl_data->type; hdr->abi = SOF_ABI_VERSION; } /* Returns the number of bytes written to the control buffer */ static int read_setup(struct ctl_data *ctl_data) { struct sof_abi_hdr *hdr = (struct sof_abi_hdr *)&ctl_data->buffer[BUFFER_ABI_OFFSET]; int n_max = ctl_data->ctrl_size; char *mode = ctl_data->binary ? "rb" : "r"; int abi_size = 0; uint32_t x; int separator; int n = 0; FILE *fh; int data_start_int_index = BUFFER_ABI_OFFSET; int data_int_index; /* open input file */ fh = fdopen(ctl_data->in_fd, mode); if (!fh) { fprintf(stderr, "error: %s\n", strerror(errno)); return -errno; } /* create abi header*/ if (ctl_data->no_abi) { header_init(ctl_data); abi_size = sizeof(struct sof_abi_hdr); data_start_int_index += abi_size / sizeof(uint32_t); } if (ctl_data->binary) { n = fread(&ctl_data->buffer[data_start_int_index], sizeof(uint8_t), n_max - abi_size, fh); goto read_done; } /* reading for ASCII CSV txt */ data_int_index = data_start_int_index; while (fscanf(fh, "%u", &x) != EOF) { if (n < n_max) ctl_data->buffer[data_int_index] = x; if (n > 0) fprintf(stdout, ","); fprintf(stdout, "%u", x); separator = fgetc(fh); while (separator != ',' && separator != EOF) separator = fgetc(fh); data_int_index++; n += sizeof(uint32_t); } fprintf(stdout, "\n"); read_done: if (ctl_data->no_abi) { hdr->size = n; n += abi_size; } if (n > n_max) { fprintf(stderr, "Warning: Read of %d exceeded control size. ", n); fprintf(stderr, "Please check the data file.\n"); } if (hdr->magic != ctl_data->magic) fprintf(stderr, "Info: ABI mismatch: expecting: 0x%8.8x, got: 0x%8.8x\n", ctl_data->magic, hdr->magic); fclose(fh); return n; } static void header_dump(struct ctl_data *ctl_data) { struct sof_abi_hdr *hdr = (struct sof_abi_hdr *)&ctl_data->buffer[BUFFER_ABI_OFFSET]; fprintf(stdout, "hdr: magic 0x%8.8x\n", hdr->magic); if (ctl_data->ipc_type == SOF_IPC_TYPE_3) fprintf(stdout, "hdr: type %u\n", hdr->type); else fprintf(stdout, "hdr: param_id %u\n", hdr->type); fprintf(stdout, "hdr: size %d bytes\n", hdr->size); fprintf(stdout, "hdr: abi %d:%d:%d\n", SOF_ABI_VERSION_MAJOR(hdr->abi), SOF_ABI_VERSION_MINOR(hdr->abi), SOF_ABI_VERSION_PATCH(hdr->abi)); } /* dump binary data out with 16bit hex format */ static void hex_data_dump(struct ctl_data *ctl_data) { unsigned int int_offset; uint16_t *config; int n; int i; /* calculate the dumping units */ n = ctl_data->buffer[BUFFER_SIZE_OFFSET] / sizeof(uint16_t); /* exclude the type and size header */ int_offset = 2; /* exclude abi header if '-r' specified */ if (ctl_data->no_abi) { int_offset += sizeof(struct sof_abi_hdr) / sizeof(uint32_t); n -= sizeof(struct sof_abi_hdr) / sizeof(uint16_t); } /* get the dumping start address */ config = (uint16_t *)&ctl_data->buffer[int_offset]; /* Print out in 16bit hex format */ for (i = 0; i < n; i++) { if (!(i % 8)) fprintf(stdout, "%08zx ", i * sizeof(uint16_t)); fprintf(stdout, "%04x ", config[i]); if ((i % 8) == 7) fprintf(stdout, "\n"); } fprintf(stdout, "\n"); } /* dump binary data out with CSV txt format */ static void csv_data_dump(struct ctl_data *ctl_data, FILE *fh) { uint32_t *config; int n; int i; int s = 0; config = &ctl_data->buffer[BUFFER_ABI_OFFSET]; n = ctl_data->buffer[BUFFER_SIZE_OFFSET] / sizeof(uint32_t); if (ctl_data->no_abi) s = sizeof(struct sof_abi_hdr) / sizeof(uint32_t); /* Print out in CSV txt formal */ for (i = s; i < n; i++) { if (i == n - 1) fprintf(fh, "%u\n", config[i]); else fprintf(fh, "%u,", config[i]); } } /* * Print the read kcontrol configuration data with either * 16bit Hex binary format or ASCII CSV format. */ static void data_dump(struct ctl_data *ctl_data) { if (ctl_data->binary) hex_data_dump(ctl_data); else csv_data_dump(ctl_data, stdout); } static int get_file_size(int fd) { struct stat st; int ret; if (fstat(fd, &st) == -1) ret = -EINVAL; else ret = st.st_size; return ret; } static int buffer_alloc(struct ctl_data *ctl_data) { int buffer_size; /* * Allocate buffer for tlv write/read. The buffer needs a two * words header with tag (SOF_CTRL_CMD_BINARY) and size in bytes. */ buffer_size = ctl_data->ctrl_size + 2 * sizeof(uint32_t); ctl_data->buffer = calloc(1, buffer_size); if (!ctl_data->buffer) { fprintf(stderr, "Error: Failed to allocate buffer for user data.\n"); return -EINVAL; } ctl_data->buffer[BUFFER_TAG_OFFSET] = SOF_CTRL_CMD_BINARY; ctl_data->buffer_size = buffer_size; return 0; } static void buffer_free(struct ctl_data *ctl_data) { free(ctl_data->buffer); ctl_data->buffer_size = 0; } static int ctl_setup(struct ctl_data *ctl_data) { int mode = SND_CTL_NONBLOCK; int ctrl_size; int read; int write; int type; int ret; /* Open the device, mixer control and get read/write/type properties. */ ret = snd_ctl_open(&ctl_data->ctl, ctl_data->dev, mode); if (ret) { fprintf(stderr, "Error: Could not open device %s.\n", ctl_data->dev); return ret; } /* Allocate buffers for pointers info, id, and value. */ ret = snd_ctl_elem_info_malloc(&ctl_data->info); if (ret) { fprintf(stderr, "Error: Could not malloc elem_info!\n"); goto ctl_close; } ret = snd_ctl_elem_id_malloc(&ctl_data->id); if (ret) { fprintf(stderr, "Error: Could not malloc elem_id!\n"); goto info_free; } ret = snd_ctl_elem_value_malloc(&ctl_data->value); if (ret) { fprintf(stderr, "Error: Could not malloc elem_value!\n"); goto id_free; } /* Get handle id for the ascii control name. */ ret = snd_ctl_ascii_elem_id_parse(ctl_data->id, ctl_data->cname); if (ret) { fprintf(stderr, "Error: Can't find %s.\n", ctl_data->cname); goto value_free; } /* Get handle info from id. */ snd_ctl_elem_info_set_id(ctl_data->info, ctl_data->id); ret = snd_ctl_elem_info(ctl_data->ctl, ctl_data->info); if (ret) { fprintf(stderr, "Error: Could not get elem info.\n"); goto value_free; } if (ctl_data->binary && ctl_data->set) { /* set ctrl_size to file size */ ctrl_size = get_file_size(ctl_data->in_fd); if (ctrl_size <= 0) { fprintf(stderr, "Error: Input file unavailable.\n"); goto value_free; } /* need more space for raw data file(no header in the file) */ if (ctl_data->no_abi) ctrl_size += sizeof(struct sof_abi_hdr); } else { /* Get control attributes from info. */ ctrl_size = snd_ctl_elem_info_get_count(ctl_data->info); } fprintf(stderr, "Control size is %d.\n", ctrl_size); read = snd_ctl_elem_info_is_tlv_readable(ctl_data->info); write = snd_ctl_elem_info_is_tlv_writable(ctl_data->info); type = snd_ctl_elem_info_get_type(ctl_data->info); if (!read && !write) { fprintf(stderr, "Error: Not a read/write control\n"); goto value_free; } if (type != SND_CTL_ELEM_TYPE_BYTES) { fprintf(stderr, "Error: control type has no bytes support.\n"); goto value_free; } ctl_data->ctrl_size = ctrl_size; /* allocate buffer for tlv data */ ret = buffer_alloc(ctl_data); if (ret < 0) { fprintf(stderr, "Error: Could not allocate buffer, ret:%d\n", ret); goto value_free; } return ret; value_free: snd_ctl_elem_value_free(ctl_data->value); id_free: snd_ctl_elem_id_free(ctl_data->id); info_free: snd_ctl_elem_info_free(ctl_data->info); ctl_close: ret |= snd_ctl_close(ctl_data->ctl); return ret; } static int ctl_free(struct ctl_data *ctl_data) { int ret; buffer_free(ctl_data); snd_ctl_elem_value_free(ctl_data->value); snd_ctl_elem_id_free(ctl_data->id); snd_ctl_elem_info_free(ctl_data->info); ret = snd_ctl_close(ctl_data->ctl); return ret; } static void ctl_dump(struct ctl_data *ctl_data) { FILE *fh; int offset = 0; size_t n = 0;/* in bytes */ if (ctl_data->out_fd > 0) { if (ctl_data->binary) { /* output ctl_data(exclude the header)to file */ /* open input file */ fh = fdopen(ctl_data->out_fd, "wb"); if (!fh) { fprintf(stderr, "error: %s\n", strerror(errno)); return; } offset = BUFFER_ABI_OFFSET; n = ctl_data->buffer[BUFFER_SIZE_OFFSET]; if (ctl_data->no_abi) { offset += sizeof(struct sof_abi_hdr) / sizeof(int); n -= sizeof(struct sof_abi_hdr); } n = fwrite(&ctl_data->buffer[offset], 1, n, fh); } else { fh = fdopen(ctl_data->out_fd, "w"); if (!fh) { fprintf(stderr, "error: %s\n", strerror(errno)); return; } csv_data_dump(ctl_data, fh); } fprintf(stdout, "%zd bytes written to file.\n", n); fclose(fh); } else { /* dump to stdout */ header_dump(ctl_data); data_dump(ctl_data); } } static int ctl_set_get(struct ctl_data *ctl_data) { int ret; size_t n; if (!ctl_data->buffer) { fprintf(stderr, "Error: No buffer for set/get!\n"); return -EINVAL; } if (ctl_data->set) { fprintf(stdout, "Applying configuration \"%s\" ", ctl_data->input_file); fprintf(stdout, "into device %s control %s.\n", ctl_data->dev, ctl_data->cname); n = read_setup(ctl_data); if (n < 1) { fprintf(stderr, "Error: failed data read from %s.\n", ctl_data->input_file); return -EINVAL; } ctl_data->buffer[BUFFER_SIZE_OFFSET] = n; ret = snd_ctl_elem_tlv_write(ctl_data->ctl, ctl_data->id, ctl_data->buffer); if (ret < 0) { fprintf(stderr, "Error: failed TLV write (%d)\n", ret); return ret; } fprintf(stdout, "Success.\n"); } else { fprintf(stdout, "Retrieving configuration for "); fprintf(stdout, "device %s control %s.\n", ctl_data->dev, ctl_data->cname); ctl_data->buffer[BUFFER_SIZE_OFFSET] = ctl_data->ctrl_size; ret = snd_ctl_elem_tlv_read(ctl_data->ctl, ctl_data->id, ctl_data->buffer, ctl_data->buffer_size); if (ret < 0) { fprintf(stderr, "Error: failed TLV read.\n"); return ret; } fprintf(stdout, "Success.\n"); } return 0; } int main(int argc, char *argv[]) { char *input_file = NULL; char *output_file = NULL; struct ctl_data *ctl_data; int ipc_type = 3; char nname[256]; int ret = 0; int opt; struct sof_abi_hdr *hdr; ctl_data = calloc(1, sizeof(struct ctl_data)); if (!ctl_data) { fprintf(stderr, "Error: Failed to allocate buffer for ctl_data\n"); return -ENOMEM; } ctl_data->dev = "hw:0"; while ((opt = getopt(argc, argv, "hD:c:s:n:i:o:t:p:g:br")) != -1) { switch (opt) { case 'D': ctl_data->dev = optarg; break; case 'c': ctl_data->cname = optarg; break; case 'n': sprintf(nname, "numid=%d", atoi(optarg)); ctl_data->cname = nname; break; case 'i': ipc_type = atoi(optarg); break; case 's': input_file = optarg; ctl_data->input_file = input_file; ctl_data->set = true; break; case 'o': output_file = optarg; break; case 'b': ctl_data->binary = true; break; case 'r': ctl_data->no_abi = true; break; case 't': case 'p': ctl_data->type = atoi(optarg); break; case 'g': ctl_data->print_abi_header = true; ctl_data->print_abi_size = atoi(optarg); break; case 'h': /* pass through */ default: usage(argv[0]); goto struct_free; } } switch (ipc_type) { case 3: ctl_data->ipc_type = SOF_IPC_TYPE_3; ctl_data->magic = SOF_ABI_MAGIC; break; case 4: if (ctl_data->type > 255) { fprintf(stderr, "error: The param_id %u is out of range\n\n", ctl_data->type); usage(argv[0]); goto struct_free; } ctl_data->ipc_type = SOF_IPC_TYPE_4; ctl_data->magic = SOF_IPC4_ABI_MAGIC; break; default: fprintf(stderr, "error: Unsupported IPC type: %u\n\n", ipc_type); usage(argv[0]); goto struct_free; } /* open output file */ if (output_file) { ctl_data->out_fd = open(output_file, O_CREAT | O_TRUNC | O_RDWR, 0600); if (ctl_data->out_fd <= 0) { fprintf(stderr, "error: %s\n", strerror(errno)); goto struct_free; } } /* Just print the ABI header if requested */ if (ctl_data->print_abi_header) { ctl_data->ctrl_size = sizeof(struct sof_abi_hdr); buffer_alloc(ctl_data); header_init(ctl_data); hdr = (struct sof_abi_hdr *) &ctl_data->buffer[BUFFER_ABI_OFFSET]; hdr->size = ctl_data->print_abi_size; ctl_data->buffer[BUFFER_SIZE_OFFSET] = ctl_data->ctrl_size; ctl_dump(ctl_data); buffer_free(ctl_data); goto out_fd_close; } /* The control need to be defined. */ if (!ctl_data->cname) { fprintf(stderr, "Error: No control was requested.\n"); usage(argv[0]); goto out_fd_close; } /* open input file */ if (input_file) { ctl_data->in_fd = open(input_file, O_RDONLY); if (ctl_data->in_fd <= 0) { fprintf(stderr, "error: %s\n", strerror(errno)); goto out_fd_close; } } /* set up ctl_elem, allocate buffers */ ret = ctl_setup(ctl_data); if (ret < 0) { fprintf(stderr, "Error: ctl_data setup failed, ret:%d", ret); goto in_fd_close; } /* set/get the tlv bytes kcontrol */ ret = ctl_set_get(ctl_data); if (ret < 0) { fprintf(stderr, "Error: Could not %s control, ret:%d\n", ctl_data->set ? "set" : "get", ret); goto data_free; } /* dump the tlv buffer to a file or stdout */ ctl_dump(ctl_data); data_free: ret = ctl_free(ctl_data); in_fd_close: if (ctl_data->out_fd) close(ctl_data->out_fd); out_fd_close: if (ctl_data->in_fd) close(ctl_data->in_fd); struct_free: free(ctl_data); return ret; }