mirror of https://github.com/thesofproject/sof.git
396 lines
9.2 KiB
C
396 lines
9.2 KiB
C
// SPDX-License-Identifier: BSD-3-Clause
|
|
//
|
|
// Copyright(c) 2019 Intel Corporation. All rights reserved.
|
|
//
|
|
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
|
|
// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
|
|
|
|
/*
|
|
* Creates an IO bridge between two QEMU instances where messages can be passed
|
|
* between the parent and child instances via messages queues and shared memory.
|
|
*
|
|
* The parent is usually the QEMU instance that runs the operating system (like
|
|
* Linux) on the application processor whilst the child is typically a smaller
|
|
* processor running an embedded firmware. The parent and child do not need to
|
|
* be the same architecture but are expected to communicate over a local bus.
|
|
*/
|
|
|
|
#include <mqueue.h>
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <glib.h>
|
|
#include <errno.h>
|
|
#include "qemu-bridge.h"
|
|
|
|
/* set to 1 to enable debug */
|
|
static int io_bridge_debug;//QEMU_IO_DEBUG;
|
|
|
|
/* we can either be parent or child */
|
|
#define ROLE_NONE 0
|
|
#define ROLE_PARENT 1
|
|
#define ROLE_CHILD 2
|
|
|
|
static int role = ROLE_NONE;
|
|
|
|
#define QEMU_IO_MAX_MSGS 8
|
|
#define QEMU_IO_MAX_MSG_SIZE 128
|
|
#define QEMU_IO_MAX_SHM_REGIONS 32
|
|
|
|
#define NAME_SIZE 64
|
|
|
|
struct io_shm {
|
|
int fd;
|
|
void *addr;
|
|
char name[NAME_SIZE];
|
|
size_t size;
|
|
};
|
|
|
|
struct io_mq {
|
|
char mq_name[NAME_SIZE];
|
|
char thread_name[NAME_SIZE];
|
|
struct mq_attr mqattr;
|
|
mqd_t mqdes;
|
|
};
|
|
|
|
struct io_bridge {
|
|
struct io_mq parent;
|
|
struct io_mq child;
|
|
GThread *io_thread;
|
|
int (*cb)(void *data, struct qemu_io_msg *msg);
|
|
struct io_shm shm[QEMU_IO_MAX_SHM_REGIONS];
|
|
void *data;
|
|
};
|
|
|
|
static struct io_bridge _iob;
|
|
static int _id;
|
|
|
|
/* parent reader Q */
|
|
static gpointer parent_reader_thread(gpointer data)
|
|
{
|
|
struct io_bridge *io = data;
|
|
char buf[QEMU_IO_MAX_MSG_SIZE];
|
|
int i;
|
|
|
|
mq_getattr(io->parent.mqdes, &io->parent.mqattr);
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: %d messages are currently on parent queue.\n",
|
|
(int)io->parent.mqattr.mq_curmsgs);
|
|
|
|
while (mq_receive(io->parent.mqdes, buf, QEMU_IO_MAX_MSG_SIZE,
|
|
NULL) != -1) {
|
|
struct qemu_io_msg *hdr = (struct qemu_io_msg *)buf;
|
|
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: msg recv %d type %d size %d msg %d\n",
|
|
hdr->id, hdr->type, hdr->size, hdr->msg);
|
|
|
|
if (io->cb)
|
|
io->cb(io->data, hdr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* child reader Q */
|
|
static gpointer child_reader_thread(gpointer data)
|
|
{
|
|
struct io_bridge *io = data;
|
|
char buf[QEMU_IO_MAX_MSG_SIZE];
|
|
int i;
|
|
|
|
mq_getattr(io->child.mqdes, &io->child.mqattr);
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: %d messages are currently on child queue.\n",
|
|
(int)io->child.mqattr.mq_curmsgs);
|
|
|
|
/* flush old messages here */
|
|
for (i = 0; i < io->child.mqattr.mq_curmsgs; i++) {
|
|
mq_receive(io->child.mqdes, buf, QEMU_IO_MAX_MSG_SIZE, NULL);
|
|
struct qemu_io_msg *hdr = (struct qemu_io_msg *)buf;
|
|
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: flushed %d type %d size %d msg %d\n",
|
|
hdr->id, hdr->type, hdr->size, hdr->msg);
|
|
}
|
|
|
|
while (mq_receive(io->child.mqdes, buf, QEMU_IO_MAX_MSG_SIZE,
|
|
NULL) != -1) {
|
|
struct qemu_io_msg *hdr = (struct qemu_io_msg *)buf;
|
|
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: msg recv %d type %d size %d msg %d\n",
|
|
hdr->id, hdr->type, hdr->size, hdr->msg);
|
|
|
|
if (io->cb)
|
|
io->cb(io->data, hdr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mq_init(const char *name, struct io_bridge *io)
|
|
{
|
|
int ret = 0;
|
|
|
|
io->parent.mqattr.mq_maxmsg = QEMU_IO_MAX_MSGS;
|
|
io->parent.mqattr.mq_msgsize = QEMU_IO_MAX_MSG_SIZE;
|
|
io->parent.mqattr.mq_flags = 0;
|
|
io->parent.mqattr.mq_curmsgs = 0;
|
|
|
|
io->child.mqattr.mq_maxmsg = QEMU_IO_MAX_MSGS;
|
|
io->child.mqattr.mq_msgsize = QEMU_IO_MAX_MSG_SIZE;
|
|
io->child.mqattr.mq_flags = 0;
|
|
io->child.mqattr.mq_curmsgs = 0;
|
|
|
|
if (role == ROLE_PARENT) {
|
|
|
|
/* Host */
|
|
|
|
sprintf(io->parent.thread_name, "io-bridge-%s", name);
|
|
io->io_thread = g_thread_new(io->parent.thread_name,
|
|
parent_reader_thread, io);
|
|
|
|
/* parent Rx Q */
|
|
sprintf(io->parent.mq_name, "/qemu-io-parent-%s", name);
|
|
io->parent.mqdes = mq_open(io->parent.mq_name,
|
|
O_RDONLY | O_CREAT,
|
|
0664, &io->parent.mqattr);
|
|
if (io->parent.mqdes < 0) {
|
|
fprintf(stderr, "failed to open parent Rx queue %d\n",
|
|
-errno);
|
|
ret = -errno;
|
|
}
|
|
|
|
/* parent Tx Q */
|
|
sprintf(io->child.mq_name, "/qemu-io-child-%s", name);
|
|
io->child.mqdes = mq_open(io->child.mq_name,
|
|
O_WRONLY | O_CREAT,
|
|
0664, &io->child.mqattr);
|
|
if (io->child.mqdes < 0) {
|
|
fprintf(stderr, "failed to open parent Tx queue %d\n",
|
|
-errno);
|
|
ret = -errno;
|
|
}
|
|
} else {
|
|
|
|
/* DSP */
|
|
|
|
sprintf(io->child.thread_name, "io-bridge-%s", name);
|
|
io->io_thread = g_thread_new(io->child.thread_name,
|
|
child_reader_thread, io);
|
|
|
|
/* child Rx Q */
|
|
sprintf(io->child.mq_name, "/qemu-io-child-%s", name);
|
|
mq_unlink(io->child.mq_name);
|
|
io->child.mqdes = mq_open(io->child.mq_name,
|
|
O_RDONLY | O_CREAT,
|
|
0664, &io->child.mqattr);
|
|
if (io->child.mqdes < 0) {
|
|
fprintf(stderr, "failed to open child Rx queue %d\n",
|
|
-errno);
|
|
ret = -errno;
|
|
}
|
|
|
|
/* child Tx Q */
|
|
sprintf(io->parent.mq_name, "/qemu-io-parent-%s", name);
|
|
mq_unlink(io->parent.mq_name);
|
|
io->parent.mqdes = mq_open(io->parent.mq_name,
|
|
O_WRONLY | O_CREAT,
|
|
0664, &io->parent.mqattr);
|
|
if (io->parent.mqdes < 0) {
|
|
fprintf(stderr, "failed to open child Tx queue %d\n",
|
|
-errno);
|
|
ret = -errno;
|
|
}
|
|
}
|
|
|
|
if (ret == 0 && io_bridge_debug) {
|
|
fprintf(stdout, "bridge-io-mq: added %s\n",
|
|
io->parent.mq_name);
|
|
fprintf(stdout, "bridge-io-mq: added %s\n", io->child.mq_name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int qemu_io_register_parent(const char *name,
|
|
int (*cb)(void *, struct qemu_io_msg *msg),
|
|
void *data)
|
|
{
|
|
if (role != ROLE_NONE)
|
|
return -EINVAL;
|
|
|
|
role = ROLE_PARENT;
|
|
_iob.cb = cb;
|
|
_iob.data = data;
|
|
|
|
mq_init(name, &_iob);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qemu_io_register_child(const char *name,
|
|
int (*cb)(void *, struct qemu_io_msg *msg),
|
|
void *data)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (role != ROLE_NONE)
|
|
return -EINVAL;
|
|
|
|
role = ROLE_CHILD;
|
|
_iob.cb = cb;
|
|
_iob.data = data;
|
|
|
|
mq_init(name, &_iob);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int qemu_io_register_shm(const char *rname, int region, size_t size,
|
|
void **addr)
|
|
{
|
|
char *name;
|
|
int fd, ret;
|
|
void *a;
|
|
|
|
/* check that region is not already in use */
|
|
if (_iob.shm[region].fd)
|
|
return -EBUSY;
|
|
|
|
name = _iob.shm[region].name;
|
|
sprintf(name, "qemu-bridge-%s", rname);
|
|
|
|
fd = shm_open(name, O_RDWR | O_CREAT, 0664);
|
|
if (fd < 0) {
|
|
fprintf(stderr, "bridge-io: can't open SHM %d\n", errno);
|
|
return -errno;
|
|
}
|
|
|
|
ret = ftruncate(fd, size);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "bridge-io: can't truncate %d\n", errno);
|
|
shm_unlink(name);
|
|
return -errno;
|
|
}
|
|
|
|
a = mmap(*addr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (!a) {
|
|
fprintf(stderr, "bridge-io: can't open mmap %d\n", errno);
|
|
shm_unlink(name);
|
|
return -errno;
|
|
}
|
|
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: %s fd %d region %d at %p allocated %zu bytes\n",
|
|
name, fd, region, a, size);
|
|
_iob.shm[region].fd = fd;
|
|
_iob.shm[region].addr = a;
|
|
_iob.shm[region].size = size;
|
|
*addr = a;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define PAGE_SIZE 4096
|
|
|
|
int qemu_io_sync(int region, unsigned int offset, size_t length)
|
|
{
|
|
if (region < 0 || region > QEMU_IO_MAX_SHM_REGIONS)
|
|
return -EINVAL;
|
|
|
|
/* check that region is in use */
|
|
if (_iob.shm[region].fd == 0)
|
|
return -EINVAL;
|
|
|
|
/* align offset to pagesize */
|
|
offset -= (offset % PAGE_SIZE);
|
|
|
|
return msync(_iob.shm[region].addr + offset, length,
|
|
MS_SYNC | MS_INVALIDATE);
|
|
}
|
|
|
|
int qemu_io_send_msg(struct qemu_io_msg *msg)
|
|
{
|
|
int ret;
|
|
|
|
msg->id = _id++;
|
|
|
|
if (role == ROLE_PARENT)
|
|
ret = mq_send(_iob.child.mqdes, (const char *)msg, msg->size,
|
|
0);
|
|
else
|
|
ret = mq_send(_iob.parent.mqdes, (const char *)msg, msg->size,
|
|
0);
|
|
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: msg send: %d type %d msg %d size %d ret %d\n",
|
|
msg->id, msg->type, msg->msg, msg->size, ret);
|
|
if (ret < 0)
|
|
fprintf(stderr, "bridge-io: msg send failed %d\n", -errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int qemu_io_send_msg_reply(struct qemu_io_msg *msg)
|
|
{
|
|
int ret;
|
|
|
|
if (role == ROLE_PARENT)
|
|
ret = mq_send(_iob.child.mqdes, (const char *)msg, msg->size,
|
|
0);
|
|
else
|
|
ret = mq_send(_iob.parent.mqdes, (const char *)msg, msg->size,
|
|
0);
|
|
|
|
if (io_bridge_debug)
|
|
fprintf(stdout, "bridge-io: repmsg send: %d type %d msg %d size %d ret %d\n",
|
|
msg->id, msg->type, msg->msg, msg->size, ret);
|
|
if (ret < 0)
|
|
fprintf(stderr, "bridge-io: rmsg send failed %d\n", -errno);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void qemu_io_free(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < QEMU_IO_MAX_SHM_REGIONS; i++) {
|
|
if (_iob.shm[i].fd) {
|
|
munmap(_iob.shm[i].addr, _iob.shm[i].size);
|
|
shm_unlink(_iob.shm[i].name);
|
|
close(_iob.shm[i].fd);
|
|
}
|
|
}
|
|
|
|
mq_unlink(_iob.parent.mq_name);
|
|
mq_unlink(_iob.child.mq_name);
|
|
|
|
mq_close(_iob.parent.mqdes);
|
|
mq_close(_iob.child.mqdes);
|
|
}
|
|
|
|
void qemu_io_free_shm(int region)
|
|
{
|
|
int err;
|
|
|
|
if (region < QEMU_IO_MAX_SHM_REGIONS && _iob.shm[region].fd) {
|
|
err = munmap(_iob.shm[region].addr, _iob.shm[region].size);
|
|
if (err < 0)
|
|
fprintf(stderr, "bridge-io: munmap failed %d\n",
|
|
errno);
|
|
|
|
/* client or host can unlink this, so it gets done twice */
|
|
shm_unlink(_iob.shm[region].name);
|
|
close(_iob.shm[region].fd);
|
|
_iob.shm[region].fd = 0;
|
|
}
|
|
}
|