sof/tools/fuzzer/platform/byt-host.c

538 lines
14 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>
/* Core IA host SHIM support for Baytrail audio DSP. */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/time.h>
#include "shim.h"
#include <ipc/trace.h>
#include <ipc/info.h>
#include "../fuzzer.h"
#include "../qemu-bridge.h"
#define MBOX_OFFSET 0x144000
/* Baytrail, Cherrytrail and Braswell - taken from qemu */
#define ADSP_PCI_SIZE 0x00001000
#define ADSP_BYT_PCI_BASE 0xF1200000
#define ADSP_BYT_MMIO_BASE 0xF1400000
#define ADSP_BYT_HOST_IRAM_OFFSET 0x000c0000
#define ADSP_BYT_HOST_DRAM_OFFSET 0x00100000
#define ADSP_BYT_HOST_IRAM_BASE \
(ADSP_BYT_MMIO_BASE + ADSP_BYT_HOST_IRAM_OFFSET)
#define ADSP_BYT_HOST_DRAM_BASE \
(ADSP_BYT_MMIO_BASE + ADSP_BYT_HOST_DRAM_OFFSET)
#define ADSP_BYT_HOST_SHIM_BASE \
(ADSP_BYT_MMIO_BASE + 0x00140000)
#define ADSP_BYT_HOST_MAILBOX_BASE \
(ADSP_BYT_MMIO_BASE + 0x00144000)
#define ADSP_CHT_PCI_BASE 0xF1600000
#define ADSP_CHT_MMIO_BASE 0xF1800000
#define ADSP_CHT_HOST_IRAM_BASE \
(ADSP_CHT_MMIO_BASE + ADSP_BYT_HOST_IRAM_OFFSET)
#define ADSP_CHT_HOST_DRAM_BASE \
(ADSP_CHT_MMIO_BASE + ADSP_BYT_HOST_DRAM_OFFSET)
#define ADSP_CHT_HOST_SHIM_BASE \
(ADSP_CHT_MMIO_BASE + 0x00140000)
#define ADSP_CHT_HOST_MAILBOX_BASE \
(ADSP_CHT_MMIO_BASE + 0x00144000)
#define ADSP_BYT_IRAM_SIZE 0x14000
#define ADSP_BYT_DRAM_SIZE 0x28000
#define ADSP_BYT_SHIM_SIZE 0x1000
#define ADSP_MAILBOX_SIZE 0x1000
/* TODO get from driver. */
#define BYT_PANIC_OFFSET(x) (x)
struct byt_data {
void *bar[MAX_BAR_COUNT];
struct mailbox host_box;
struct mailbox dsp_box;
int boot_complete;
pthread_mutex_t mutex;
};
/* Platform host description taken from Qemu - mapped to BAR 0, 1*/
static struct fuzzer_mem_desc byt_mem[] = {
{.name = "iram", .base = ADSP_BYT_HOST_IRAM_BASE,
.size = ADSP_BYT_IRAM_SIZE},
{.name = "dram", .base = ADSP_BYT_HOST_DRAM_BASE,
.size = ADSP_BYT_DRAM_SIZE},
};
/* mapped to BAR 2, 3 */
static struct fuzzer_reg_space byt_io[] = {
{ .name = "shim",
.desc = {.base = ADSP_BYT_HOST_SHIM_BASE,
.size = ADSP_BYT_SHIM_SIZE},},
{ .name = "mbox",
.desc = {.base = ADSP_BYT_HOST_MAILBOX_BASE,
.size = ADSP_MAILBOX_SIZE},},
};
#define BYT_DSP_BAR 2
#define BYT_MBOX_BAR 3
/*
* Platform support for BYT/CHT.
*
* The IPC portions below are copied and pasted from the SOF driver with some
* modification for data structure and printing.
*
* The "driver" code below no longer writes directly to the HW but writes
* to the virtual HW as exported by qemu as Posix SHM and message queues.
*
* Register IO and mailbox IO is performed using shared memory regions between
* fuzzer and qemu.
*
* IRQs are send using message queues between fuzzer and qemu.
*
* SHM and message queues can be inspected from the cmd line by using
* "less -C" on /dev/shm/name and /dev/mqueue/name
*/
static uint64_t dsp_read64(struct fuzz *fuzzer, unsigned int bar,
unsigned int reg)
{
struct byt_data *data = fuzzer->platform_data;
return *((uint64_t *)(data->bar[bar] + reg));
}
static void dsp_write64(struct fuzz *fuzzer, unsigned int bar,
unsigned int reg, uint64_t value)
{
struct byt_data *data = fuzzer->platform_data;
struct qemu_io_msg_irq irq;
uint32_t active, isrd;
/* write value to SHM */
*((uint64_t *)(data->bar[bar] + reg)) = value;
/* most IO is handled by SHM, but there are some exceptions */
switch (reg) {
case SHIM_IPCX:
/* now set/clear status bit */
isrd = dsp_read64(fuzzer, bar, SHIM_ISRD) &
~(SHIM_ISRD_DONE | SHIM_ISRD_BUSY);
isrd |= value & SHIM_IPCX_BUSY ? SHIM_ISRD_BUSY : 0;
isrd |= value & SHIM_IPCX_DONE ? SHIM_ISRD_DONE : 0;
dsp_write64(fuzzer, bar, SHIM_ISRD, isrd);
/* do we need to send an IRQ ? */
if (value & SHIM_IPCX_BUSY) {
fprintf(stdout, "irq: send busy interrupt 0x%8.8lx\n",
value);
/* send IRQ to child */
irq.hdr.type = QEMU_IO_TYPE_IRQ;
irq.hdr.msg = QEMU_IO_MSG_IRQ;
irq.hdr.size = sizeof(irq);
irq.irq = 0;
qemu_io_send_msg(&irq.hdr);
}
break;
case SHIM_IPCD:
/* set/clear status bit */
isrd = dsp_read64(fuzzer, bar, SHIM_ISRD) &
~(SHIM_ISRD_DONE | SHIM_ISRD_BUSY);
isrd |= value & SHIM_IPCD_BUSY ? SHIM_ISRD_BUSY : 0;
isrd |= value & SHIM_IPCD_DONE ? SHIM_ISRD_DONE : 0;
dsp_write64(fuzzer, bar, SHIM_ISRD, isrd);
/* do we need to send an IRQ ? */
if (value & SHIM_IPCD_DONE) {
fprintf(stdout, "irq: send done interrupt 0x%8.8lx\n",
value);
/* send IRQ to child */
irq.hdr.type = QEMU_IO_TYPE_IRQ;
irq.hdr.msg = QEMU_IO_MSG_IRQ;
irq.hdr.size = sizeof(irq);
irq.irq = 0;
qemu_io_send_msg(&irq.hdr);
}
break;
case SHIM_IMRX:
active = dsp_read64(fuzzer, bar, SHIM_ISRX) &
~(dsp_read64(fuzzer, bar, SHIM_IMRX));
fprintf(stdout, "irq: masking %lx mask %lx active %x\n",
dsp_read64(fuzzer, bar, SHIM_ISRD),
dsp_read64(fuzzer, bar, SHIM_IMRD), active);
break;
default:
break;
}
}
static uint64_t dsp_update_bits64_unlocked(struct fuzz *fuzzer,
unsigned int bar, uint32_t offset,
uint64_t mask, uint64_t value)
{
uint64_t old, new;
uint64_t ret;
ret = dsp_read64(fuzzer, bar, offset);
old = ret;
new = (old & ~mask) | (value & mask);
if (old == new)
return 0;
dsp_write64(fuzzer, bar, offset, new);
return 1;
}
static void mailbox_read(struct fuzz *fuzzer, unsigned int offset,
void *mbox_data, unsigned int size)
{
struct byt_data *data = fuzzer->platform_data;
memcpy(mbox_data, (void *)(data->bar[BYT_MBOX_BAR] + offset), size);
}
static void mailbox_write(struct fuzz *fuzzer, unsigned int offset,
void *mbox_data, unsigned int size)
{
struct byt_data *data = fuzzer->platform_data;
memcpy((void *)(data->bar[BYT_MBOX_BAR] + offset), mbox_data, size);
}
static int byt_cmd_done(struct fuzz *fuzzer, int dir)
{
if (dir == SOF_IPC_HOST_REPLY) {
/* clear BUSY bit and set DONE bit - accept new messages */
dsp_update_bits64_unlocked(fuzzer, BYT_DSP_BAR, SHIM_IPCD,
SHIM_BYT_IPCD_BUSY |
SHIM_BYT_IPCD_DONE,
SHIM_BYT_IPCD_DONE);
/* unmask busy interrupt */
dsp_update_bits64_unlocked(fuzzer, BYT_DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY, 0);
} else {
/* clear DONE bit - tell DSP we have completed */
dsp_update_bits64_unlocked(fuzzer, BYT_DSP_BAR, SHIM_IPCX,
SHIM_BYT_IPCX_DONE, 0);
/* unmask Done interrupt */
dsp_update_bits64_unlocked(fuzzer, BYT_DSP_BAR, SHIM_IMRX,
SHIM_IMRX_DONE, 0);
}
return 0;
}
/*
* IPC Doorbell IRQ handler and thread.
*/
static int byt_irq_handler(int irq, void *context)
{
struct fuzz *fuzzer = (struct fuzz *)context;
uint64_t isr;
int ret = IRQ_NONE;
/* Interrupt arrived, check src */
isr = dsp_read64(fuzzer, BYT_DSP_BAR, SHIM_ISRX);
if (isr & (SHIM_ISRX_DONE | SHIM_ISRX_BUSY))
ret = IRQ_WAKE_THREAD;
return ret;
}
static int byt_irq_thread(int irq, void *context)
{
struct fuzz *fuzzer = (struct fuzz *)context;
struct byt_data *data = fuzzer->platform_data;
uint64_t ipcx, ipcd;
uint64_t imrx;
imrx = dsp_read64(fuzzer, BYT_DSP_BAR, SHIM_IMRX);
ipcx = dsp_read64(fuzzer, BYT_DSP_BAR, SHIM_IPCX);
/* reply message from DSP */
if ((ipcx & SHIM_BYT_IPCX_DONE) &&
!(imrx & SHIM_IMRX_DONE)) {
/* Mask Done interrupt before first */
dsp_update_bits64_unlocked(fuzzer, BYT_DSP_BAR,
SHIM_IMRX,
SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
fprintf(stdout, "ipc: reply msg from DSP\n");
/*
* handle immediate reply from DSP core. If the msg is
* found, set done bit in cmd_done which is called at the
* end of message processing function, else set it here
* because the done bit can't be set in cmd_done function
* which is triggered by msg
*/
fuzzer_ipc_msg_reply(fuzzer, &data->host_box);
byt_cmd_done(fuzzer, SOF_IPC_DSP_REPLY);
return IRQ_HANDLED;
}
/* new message from DSP */
ipcd = dsp_read64(fuzzer, BYT_DSP_BAR, SHIM_IPCD);
if ((ipcd & SHIM_BYT_IPCD_BUSY) &&
!(imrx & SHIM_IMRX_BUSY)) {
/* Mask Busy interrupt before return */
dsp_update_bits64_unlocked(fuzzer, BYT_DSP_BAR,
SHIM_IMRX,
SHIM_IMRX_BUSY,
SHIM_IMRX_BUSY);
/* read mailbox */
if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
fuzzer_ipc_crash(fuzzer, &data->dsp_box,
BYT_PANIC_OFFSET(ipcd) + MBOX_OFFSET);
} else {
fuzzer_ipc_msg_rx(fuzzer, &data->dsp_box);
}
if (!data->boot_complete && fuzzer->boot_complete) {
data->boot_complete = 1;
byt_cmd_done(fuzzer, SOF_IPC_HOST_REPLY);
pthread_cond_signal(&cond);
return IRQ_HANDLED;
}
}
return IRQ_HANDLED;
}
static int byt_send_msg(struct fuzz *fuzzer, struct ipc_msg *msg)
{
struct byt_data *data = fuzzer->platform_data;
uint64_t cmd = msg->header;
/* send the message */
fuzzer_mailbox_write(fuzzer, &data->host_box, 0, msg->msg_data,
msg->msg_size);
dsp_write64(fuzzer, BYT_DSP_BAR, SHIM_IPCX,
cmd | SHIM_BYT_IPCX_BUSY);
return 0;
}
static int byt_get_reply(struct fuzz *fuzzer, struct ipc_msg *msg)
{
struct byt_data *data = fuzzer->platform_data;
struct sof_ipc_reply reply;
int ret = 0;
uint32_t size;
/* get reply */
fuzzer_mailbox_read(fuzzer, &data->host_box, 0, &reply, sizeof(reply));
if (reply.error < 0) {
size = sizeof(reply);
ret = reply.error;
} else {
/* reply correct size ? */
if (reply.hdr.size != msg->reply_size) {
fprintf(stderr,
"error: reply expected 0x%x got 0x%x bytes\n",
msg->reply_size, reply.hdr.size);
size = msg->reply_size;
ret = -EINVAL;
} else {
size = reply.hdr.size;
}
}
/* read the message */
if (msg->msg_data && size > 0)
fuzzer_mailbox_read(fuzzer, &data->host_box, 0,
msg->reply_data, size);
return ret;
}
/* called when we receive a message from qemu */
static int bridge_cb(void *data, struct qemu_io_msg *msg)
{
struct fuzz *fuzzer = (struct fuzz *)data;
fprintf(stdout, "msg: id %d msg %d size %d type %d\n",
msg->id, msg->msg, msg->size, msg->type);
switch (msg->type) {
case QEMU_IO_TYPE_IRQ:
/* IRQ from DSP */
if (byt_irq_handler(0, fuzzer) != IRQ_NONE)
byt_irq_thread(0, fuzzer);
break;
default:
break;
}
return 0;
}
static int byt_platform_init(struct fuzz *fuzzer,
struct fuzz_platform *platform)
{
struct timespec timeout;
struct byt_data *data;
struct timeval tp;
int i, bar;
int ret = 0;
/* init private data */
data = calloc(sizeof(*data), 1);
if (!data)
return -ENOMEM;
fuzzer->platform_data = data;
fuzzer->platform = platform;
/* create SHM for memories and register regions */
for (i = 0, bar = 0; i < platform->num_mem_regions; i++, bar++) {
data->bar[bar] = fuzzer_create_memory_region(fuzzer, bar, i);
if (!data->bar[bar]) {
fprintf(stderr, "error: failed to create mem region %s\n",
platform->mem_region[i].name);
return -ENOMEM;
}
}
for (i = 0; i < platform->num_reg_regions; i++, bar++) {
data->bar[bar] = fuzzer_create_io_region(fuzzer, bar, i);
if (!data->bar[bar]) {
fprintf(stderr, "error: failed to create mem region %s\n",
platform->reg_region[i].name);
return -ENOMEM;
}
}
/* initialise bridge to qemu */
qemu_io_register_parent(platform->name, &bridge_cb, (void *)fuzzer);
/* set boot wait timeout */
gettimeofday(&tp, NULL);
timeout.tv_sec = tp.tv_sec;
timeout.tv_nsec = tp.tv_usec * 1000;
timeout.tv_sec += 5;
/* first lock the boot wait mutex */
pthread_mutex_lock(&data->mutex);
/* now wait for mutex to be unlocked by boot ready message */
while (!ret && !data->boot_complete)
ret = pthread_cond_timedwait(&cond, &data->mutex, &timeout);
if (ret == ETIMEDOUT && !data->boot_complete)
fprintf(stderr, "error: DSP boot timeout\n");
pthread_mutex_unlock(&data->mutex);
return ret;
}
static void byt_platform_free(struct fuzz *fuzzer)
{
struct byt_data *data = fuzzer->platform_data;
fuzzer_free_regions(fuzzer);
free(data);
}
static void byt_fw_ready(struct fuzz *fuzzer)
{
struct byt_data *data = fuzzer->platform_data;
struct sof_ipc_fw_ready fw_ready;
struct sof_ipc_fw_version version;
/* read fw_ready data from mailbox */
fuzzer_mailbox_read(fuzzer, &data->dsp_box, 0,
&fw_ready, sizeof(fw_ready));
/*
* Hardcode offsets.
* TODO: read init host_box and dsp_box from fw_ready message
*/
data->host_box.offset = 0x400;
data->host_box.size = 0x400;
data->dsp_box.offset = 0;
data->dsp_box.size = 0x400;
fprintf(stdout,
"ipc: host box 0x%x size 0x%x\n", data->host_box.offset,
data->host_box.size);
fprintf(stdout, "ipc: dsp box 0x%x size 0x%x\n", data->dsp_box.offset,
data->dsp_box.size);
version = fw_ready.version;
fprintf(stdout, "ipc: FW version major: %d minor: %d tag: %s\n",
version.major, version.minor, version.tag);
}
struct fuzz_platform byt_platform = {
.name = "byt",
.send_msg = byt_send_msg,
.get_reply = byt_get_reply,
.init = byt_platform_init,
.free = byt_platform_free,
.mailbox_read = mailbox_read,
.mailbox_write = mailbox_write,
.fw_ready = byt_fw_ready,
.num_mem_regions = ARRAY_SIZE(byt_mem),
.mem_region = byt_mem,
.num_reg_regions = ARRAY_SIZE(byt_io),
.reg_region = byt_io,
};
struct fuzz_platform cht_platform = {
.name = "cht",
.send_msg = byt_send_msg,
.get_reply = byt_get_reply,
.init = byt_platform_init,
.free = byt_platform_free,
.mailbox_read = mailbox_read,
.mailbox_write = mailbox_write,
.fw_ready = byt_fw_ready,
.num_mem_regions = ARRAY_SIZE(byt_mem),
.mem_region = byt_mem,
.num_reg_regions = ARRAY_SIZE(byt_io),
.reg_region = byt_io,
};
struct fuzz_platform bsw_platform = {
.name = "bsw",
.send_msg = byt_send_msg,
.get_reply = byt_get_reply,
.init = byt_platform_init,
.free = byt_platform_free,
.mailbox_read = mailbox_read,
.mailbox_write = mailbox_write,
.fw_ready = byt_fw_ready,
.num_mem_regions = ARRAY_SIZE(byt_mem),
.mem_region = byt_mem,
.num_reg_regions = ARRAY_SIZE(byt_io),
.reg_region = byt_io,
};