// SPDX-License-Identifier: BSD-3-Clause // // Copyright(c) 2022 Intel Corporation. All rights reserved. // // Author: Liam Girdwood /* * SOF pipeline in userspace. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "pipe.h" // TODO: take prefix from ALSA prefix #define COMP_PREFIX "./sof_ep/install/lib/libsof_" #define COMP_SUFFIX ".so" #define UUID_STR_SIZE 32 struct sof_pipe_module_library_map { int module_id; const char *name; }; static const struct sof_pipe_module_library_map library_map[] = { {0x6, "libsof_volume.so"}, {0x2, "libsof_mixer.so"}, {0x3, "libsof_mixer.so"}, /*FIXME: hack for now to set up ALSA and SHM components */ {0x96, "libsof_mod_shm.so"}, /* host playback */ {0x97, "libsof_mod_alsa.so"}, /* dai playback */ {0x98, "libsof_mod_shm.so"}, /* host capture */ {0x99, "libsof_mod_alsa.so"}, /* dai capture */ }; static int pipe_register_comp(struct sof_pipe *sp, uint16_t module_id) { const struct sof_pipe_module_library_map *lib; int i; /* check if module already loaded */ for (i = 0; i < sp->mod_idx; i++) { if (sp->module[i].module_id == module_id) return 0; /* module found and already loaded */ } for (i = 0; i < ARRAY_SIZE(library_map); i++) { lib = &library_map[i]; if (module_id == lib->module_id) break; } if (i == ARRAY_SIZE(library_map)) { fprintf(stderr, "module ID: %d not supported\n", module_id); return -ENOTSUP; } /* not loaded, so load module */ sp->module[sp->mod_idx].handle = dlopen(lib->name, RTLD_NOW); if (!sp->module[sp->mod_idx].handle) { fprintf(stderr, "error: cant load module %s: %s\n", lib->name, dlerror()); return -errno; } sp->mod_idx++; return 0; } #define iCS(x) ((x) & SOF_CMD_TYPE_MASK) #define iGS(x) ((x) & SOF_GLB_TYPE_MASK) static int pipe_sof_ipc_cmd_before(struct sof_pipe *sp, void *mailbox, size_t bytes) { struct ipc4_message_request *in = mailbox; enum ipc4_message_target target = in->primary.r.msg_tgt; int ret = 0; switch (target) { case SOF_IPC4_MESSAGE_TARGET_MODULE_MSG: { uint32_t type = in->primary.r.type; switch (type) { case SOF_IPC4_MOD_INIT_INSTANCE: struct ipc4_module_init_instance *module_init = (struct ipc4_module_init_instance *)in; ret = pipe_register_comp(sp, module_init->primary.r.module_id); break; default: break; } break; } case SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG: { uint32_t type = in->primary.r.type; switch (type) { case SOF_IPC4_GLB_SET_PIPELINE_STATE: { struct ipc4_pipeline_set_state *state; struct ipc_comp_dev *ipc_pipe; struct ipc *ipc = ipc_get(); unsigned int pipeline_id; state = (struct ipc4_pipeline_set_state *)in; pipeline_id = (unsigned int)state->primary.r.ppl_id; if (state->primary.r.ppl_state == SOF_IPC4_PIPELINE_STATE_PAUSED) { ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); if (!ipc_pipe) { fprintf(stderr, "No pipeline with instance_id = %u", pipeline_id); return -EINVAL; } /* stop the pipeline thread */ ret = pipe_thread_stop(sp, ipc_pipe->pipeline); if (ret < 0) { printf("error: can't start pipeline %d thread\n", ipc_pipe->pipeline->comp_id); return ret; } } break; } default: break; } break; } default: fprintf(sp->log, "ipc: unknown command target %u size %ld", target, bytes); ret = -EINVAL; break; } return ret; } static int pipe_sof_ipc_cmd_after(struct sof_pipe *sp, void *mailbox, size_t bytes) { struct ipc4_message_request *in = mailbox; enum ipc4_message_target target = in->primary.r.msg_tgt; int ret = 0; switch (target) { case SOF_IPC4_MESSAGE_TARGET_MODULE_MSG: break; case SOF_IPC4_MESSAGE_TARGET_FW_GEN_MSG: { uint32_t type = in->primary.r.type; switch (type) { case SOF_IPC4_GLB_CREATE_PIPELINE: { struct ipc4_pipeline_create *pipe_desc = (struct ipc4_pipeline_create *)in; struct ipc_comp_dev *ipc_pipe; struct ipc *ipc = ipc_get(); unsigned int pipeline_id = (unsigned int)pipe_desc->primary.r.instance_id; ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); if (!ipc_pipe) { fprintf(stderr, "No pipeline with instance_id = %u\n", pipeline_id); return -EINVAL; } /* create new pipeline thread */ ret = pipe_thread_new(sp, ipc_pipe->pipeline); if (ret < 0) { printf("error: can't create pipeline %d thread\n", ipc_pipe->pipeline->pipeline_id); return ret; } break; } case SOF_IPC4_GLB_SET_PIPELINE_STATE: { struct ipc4_pipeline_set_state *state; struct ipc_comp_dev *ipc_pipe; struct ipc *ipc = ipc_get(); unsigned int pipeline_id; state = (struct ipc4_pipeline_set_state *)in; pipeline_id = (unsigned int)state->primary.r.ppl_id; if (state->primary.r.ppl_state == SOF_IPC4_PIPELINE_STATE_RUNNING) { ipc_pipe = ipc_get_pipeline_by_id(ipc, pipeline_id); if (!ipc_pipe) { fprintf(stderr, "No pipeline with instance_id = %u\n", pipeline_id); return -EINVAL; } /* start the pipeline thread */ ret = pipe_thread_start(sp, ipc_pipe->pipeline); if (ret < 0) { printf("error: can't start pipeline %d thread\n", ipc_pipe->pipeline->comp_id); return ret; } } break; } case SOF_IPC4_GLB_DELETE_PIPELINE: { struct ipc4_pipeline_create *pipe_desc = (struct ipc4_pipeline_create *)in; unsigned int pipeline_id = (unsigned int)pipe_desc->primary.r.instance_id; /* free pipeline thread */ ret = pipe_thread_free(sp, pipeline_id); if (ret < 0) { printf("error: can't free pipeline %d thread\n", pipeline_id); return ret; } break; } default: break; } break; } default: fprintf(sp->log, "ipc: unknown command target %u size %ld", target, bytes); ret = -EINVAL; break; } return ret; } int pipe_ipc_do(struct sof_pipe *sp, void *mailbox, size_t bytes) { char mailbox_copy[IPC3_MAX_MSG_SIZE] = {0}; int err = 0; /* preserve mailbox contents for local "after" config */ memcpy(mailbox_copy, mailbox, bytes); /* some IPCs require pipe to perform actions before core */ /* mailbox can be re-written here by local pipe if needed */ err = pipe_sof_ipc_cmd_before(sp, mailbox, bytes); if (err < 0) { fprintf(sp->log, "error: local IPC processing failed\n"); return err; } /* is the IPC local only or do we need send to infra ? */ err = pipe_ipc_message(sp, mailbox, bytes); if (err < 0) { fprintf(sp->log, "error: infra IPC processing failed\n"); return err; } /* some IPCs require pipe to perform actions before core */ err = pipe_sof_ipc_cmd_after(sp, mailbox_copy, bytes); if (err < 0) { fprintf(sp->log, "error: local IPC processing failed\n"); return err; } return err; } int pipe_ipc_process(struct sof_pipe *sp, struct plug_mq_desc *tx_mq, struct plug_mq_desc *rx_mq) { ssize_t ipc_size; char mailbox[IPC3_MAX_MSG_SIZE] = {0}; int err; struct timespec ts; /* IPC thread should not preempt processing thread */ err = pipe_set_ipc_lowpri(sp); if (err < 0) fprintf(sp->log, "error: cant set PCM IPC thread to low priority"); /* create the IPC message queue */ err = plug_mq_create(tx_mq); if (err < 0) { fprintf(sp->log, "error: can't create TX IPC message queue : %s\n", strerror(errno)); return -errno; } /* create the IPC message queue */ err = plug_mq_create(rx_mq); if (err < 0) { fprintf(sp->log, "error: can't create PCM IPC message queue : %s\n", strerror(errno)); return -errno; } /* let main() know we are ready */ fprintf(sp->log, "sof-pipe: IPC TX %s thread ready\n", tx_mq->queue_name); fprintf(sp->log, "sof-pipe: IPC RX %s thread ready\n", rx_mq->queue_name); /* main PCM IPC handling loop */ while (1) { memset(mailbox, 0, IPC3_MAX_MSG_SIZE); /* is client dead ? */ if (sp->glb->state == SOF_PLUGIN_STATE_DEAD) { fprintf(sp->log, "sof-pipe: IPC %s client complete\n", tx_mq->queue_name); break; } ipc_size = mq_receive(tx_mq->mq, mailbox, IPC3_MAX_MSG_SIZE, NULL); if (ipc_size < 0) { fprintf(sp->log, "error: can't read PCM IPC message queue %s : %s\n", tx_mq->queue_name, strerror(errno)); break; } /* TODO: properly validate message and continue if garbage */ if (*((uint32_t *)mailbox) == 0) { fprintf(sp->log, "sof-pipe: IPC %s garbage read\n", tx_mq->queue_name); ts.tv_sec = 0; ts.tv_nsec = 20 * 1000 * 1000; /* 20 ms */ nanosleep(&ts, NULL); continue; } /* do the message work */ //data_dump(mailbox, IPC3_MAX_MSG_SIZE); err = pipe_ipc_do(sp, mailbox, ipc_size); if (err < 0) fprintf(sp->log, "error: local IPC processing failed\n"); /* now return message completion status found in mailbox */ err = mq_send(rx_mq->mq, mailbox, IPC3_MAX_MSG_SIZE, 0); if (err < 0) { fprintf(sp->log, "error: can't send PCM IPC message queue %s : %s\n", rx_mq->queue_name, strerror(errno)); break; } } fprintf(sp->log, "***sof-pipe: IPC %s thread finished !!\n", tx_mq->queue_name); return 0; } int plug_mq_cmd(struct plug_mq_desc *ipc, void *msg, size_t len, void *reply, size_t rlen) { struct timespec ts; ssize_t ipc_size; char mailbox[IPC3_MAX_MSG_SIZE]; int err; if (len > IPC3_MAX_MSG_SIZE) { SNDERR("ipc: message too big %d\n", len); return -EINVAL; } memset(mailbox, 0, IPC3_MAX_MSG_SIZE); memcpy(mailbox, msg, len); /* wait for sof-pipe reader to consume data or timeout */ err = clock_gettime(CLOCK_REALTIME, &ts); if (err == -1) { SNDERR("ipc: cant get time: %s", strerror(errno)); return -errno; } /* IPCs should be read under 10ms */ plug_timespec_add_ms(&ts, 10); /* now return message completion status */ err = mq_timedsend(ipc->mq, mailbox, IPC3_MAX_MSG_SIZE, 0, &ts); if (err < 0) { SNDERR("error: can't send IPC message queue %s : %s\n", ipc->queue_name, strerror(errno)); return -errno; } /* wait for sof-pipe reader to consume data or timeout */ err = clock_gettime(CLOCK_REALTIME, &ts); if (err == -1) { SNDERR("ipc: cant get time: %s", strerror(errno)); return -errno; } /* IPCs should be processed under 20ms */ plug_timespec_add_ms(&ts, 20); ipc_size = mq_timedreceive(ipc->mq, mailbox, IPC3_MAX_MSG_SIZE, NULL, &ts); if (ipc_size < 0) { SNDERR("error: can't read IPC message queue %s : %s\n", ipc->queue_name, strerror(errno)); return -errno; } /* do the message work */ //printf("cmd got IPC %ld reply bytes\n", ipc_size); if (rlen && reply) memcpy(reply, mailbox, rlen); return 0; }