blob: c224b8450e8c8786e43456afba48addcd6fb572a [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2018 IBM Corp.
#define _GNU_SOURCE /* fallocate */
#include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "config.h"
#include "mboxd.h"
#include "backend.h"
#include "lpc.h"
#include "transport_mbox.h"
#include "windows.h"
#include "test/mbox.h"
#define STEP 16
void dump_buf(const void *buf, size_t len)
{
const uint8_t *buf8 = buf;
int i;
for (i = 0; i < len; i += STEP) {
int delta;
int max;
int j;
delta = len - i;
max = delta > STEP ? STEP : delta;
printf("0x%08x:\t", i);
for (j = 0; j < max; j++)
printf("0x%02x, ", buf8[i + j]);
printf("\n");
}
printf("\n");
}
/*
* Because we are using a file and not a pipe for the mbox file descriptor we
* need to handle a difference in behaviour. For a given command and response
* sequence the first 16 bytes of the file are occupied by the mbox command.
* The response occupies the following 14 bytes for a total of 30 bytes.
*
* We also have to ensure we lseek() to reset the file descriptor offset back
* to the start of the file before dispatching the mbox command.
*/
/* Macros for handling the pipe/file discrepancy */
#define RESPONSE_OFFSET 16
#define RESPONSE_SIZE 14
int mbox_cmp(struct mbox_context *context, const uint8_t *expected, size_t len)
{
struct stat details;
uint8_t *map;
int rc;
int fd;
fd = context->fds[MBOX_FD].fd;
fstat(fd, &details);
map = mmap(NULL, details.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
printf("%s:%d: details.st_size: %ld, RESPONSE_OFFSET + len: %ld\n",
__func__, __LINE__, details.st_size, RESPONSE_OFFSET + len);
assert(map != MAP_FAILED);
assert(details.st_size >= (RESPONSE_OFFSET + len));
rc = memcmp(expected, &map[RESPONSE_OFFSET], len);
if (rc != 0) {
printf("\nMBOX state (%ld):\n", details.st_size);
dump_buf(map, details.st_size);
printf("Expected response (%lu):\n", len);
dump_buf(expected, len);
}
munmap(map, details.st_size);
return rc;
}
void mbox_rspcpy(struct mbox_context *context, struct mbox_msg *msg)
{
struct stat details;
uint8_t *map;
int fd;
fd = context->fds[MBOX_FD].fd;
fstat(fd, &details);
map = mmap(NULL, details.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
assert(map != MAP_FAILED);
assert(details.st_size >= (RESPONSE_OFFSET + RESPONSE_SIZE));
memcpy(msg, &map[RESPONSE_OFFSET], RESPONSE_SIZE);
munmap(map, details.st_size);
}
int mbox_command_write(struct mbox_context *context, const uint8_t *command,
size_t len)
{
size_t remaining;
int rc;
int fd;
fd = context->fds[MBOX_FD].fd;
rc = lseek(fd, 0, SEEK_SET);
if (rc != 0)
return -1;
remaining = len;
while (remaining > 0) {
rc = write(fd, command, remaining);
if (rc < 0)
goto out;
remaining -= rc;
}
rc = lseek(fd, 0, SEEK_SET);
if (rc != 0)
return -1;
out:
return rc;
}
int mbox_command_dispatch(struct mbox_context *context, const uint8_t *command,
size_t len)
{
uint8_t status;
int rc;
rc = mbox_command_write(context, command, len);
if (rc < 0)
return rc;
rc = transport_mbox_dispatch(context);
if (rc < 0)
return -rc;
/*
* The aspeed-lpc-ctrl driver implements mailbox register access
* through the usual read()/write() chardev interface.
*
* The typical access sequence is:
*
* 1. Read all the registers out
* 2. Perform the action specified
* 3. Write a response to the registers.
*
* For the tests the "device" file descriptor is backed by a temporary
* file. The above sequence leads to a file-size of 30 bytes: 16 bytes
* for the issued command, followed by a 14-byte response.
*
* However, the typical access pattern isn't the only access pattern.
* Individual byte registers can be accessed by lseek()'ing on the
* device's file descriptor and issuing read() or write() as desired.
* The daemon uses this property to manage the BMC status byte, and the
* implementation cleans up after status byte operations by lseek()'ing
* back to offset zero.
*
* Thus for the BMC_EVENT_ACK command the file only reaches a size of
* 16 bytes; the daemon's response overwrites the first 14 bytes of the
* command injected by the tests.
*
* The consequence of this is that the response status byte can either
* appear at offset 13, or offset 29, depending on the command.
*
* However, regardless of what command is issued the response data is
* written to the device and the file descriptor is left in the
* post-write() state. This means the status byte can always be
* accessed relative to the current position by an lseek() of type
* SEEK_CUR for offset -1.
*
*/
rc = lseek(context->fds[MBOX_FD].fd, -1, SEEK_CUR);
if (rc < 0)
return rc;
rc = read(context->fds[MBOX_FD].fd, &status, sizeof(status));
if (rc < 0)
return rc;
return status;
}
struct mbox_test_context {
struct tmpf mbox;
struct tmpf flash;
struct tmpf lpc;
struct mbox_context context;
} test;
void cleanup(void)
{
tmpf_destroy(&test.mbox);
tmpf_destroy(&test.flash);
tmpf_destroy(&test.lpc);
}
int __transport_mbox_init(struct mbox_context *context, const char *path,
const struct transport_ops **ops);
int __lpc_dev_init(struct mbox_context *context, const char *path);
struct mbox_context *mbox_create_frontend_context(int n_windows, size_t len)
{
const struct transport_ops *ops;
struct mtd_info_user mtd_info;
int rc;
mbox_vlog = &mbox_log_console;
verbosity = 2;
atexit(cleanup);
rc = tmpf_init(&test.mbox, "mbox-store.XXXXXX");
assert(rc == 0);
rc = tmpf_init(&test.lpc, "lpc-store.XXXXXX");
assert(rc == 0);
test.context.windows.num = n_windows;
test.context.windows.default_size = len;
rc = protocol_init(&test.context);
assert(rc == 0);
/*
* We need to call __transport_mbox_init() to initialise the handler table.
* However, afterwards we need to discard the fd of the clearly useless
* /dev/null and replace it with our own fd for mbox device emulation
* by the test framework.
*/
__transport_mbox_init(&test.context, "/dev/null", &ops);
test.context.transport = ops;
rc = close(test.context.fds[MBOX_FD].fd);
assert(rc == 0);
test.context.fds[MBOX_FD].fd = test.mbox.fd;
/* Instantiate the mtd backend */
rc = tmpf_init(&test.flash, "flash-store.XXXXXX");
assert(rc == 0);
rc = ioctl(test.flash.fd, MEMGETINFO, &mtd_info);
assert(rc == 0);
rc = fallocate(test.flash.fd, 0, 0, mtd_info.size);
assert(rc == 0);
test.context.backend.flash_size = mtd_info.size;
rc = __lpc_dev_init(&test.context, test.lpc.path);
assert(rc == 0);
rc = fallocate(test.lpc.fd, 0, 0, test.context.mem_size);
assert(rc == 0);
rc = windows_init(&test.context);
assert(rc == 0);
return rc ? NULL : &test.context;
}
struct mbox_context *mbox_create_test_context(int n_windows, size_t len)
{
struct mbox_context *ctx;
int rc;
ctx = mbox_create_frontend_context(n_windows, len);
assert(ctx);
rc = backend_probe_mtd(&ctx->backend, test.flash.path);
assert(rc == 0);
return ctx;
}
/* From ccan's container_of module, CC0 license */
#define container_of(member_ptr, containing_type, member) \
((containing_type *) \
((char *)(member_ptr) \
- container_off(containing_type, member)) \
+ check_types_match(*(member_ptr), ((containing_type *)0)->member))
/* From ccan's container_of module, CC0 license */
#define container_off(containing_type, member) \
offsetof(containing_type, member)
/* From ccan's check_type module, CC0 license */
#define check_type(expr, type) \
((typeof(expr) *)0 != (type *)0)
/* From ccan's check_type module, CC0 license */
#define check_types_match(expr1, expr2) \
((typeof(expr1) *)0 != (typeof(expr2) *)0)
int mbox_set_mtd_data(struct mbox_context *context, const void *data,
size_t len)
{
struct mbox_test_context *arg;
void *map;
assert(test.flash.fd > 2);
/* Sanity check */
arg = container_of(context, struct mbox_test_context, context);
assert(&test == arg);
assert(len <= test.context.backend.flash_size);
map = mmap(NULL, test.context.backend.flash_size,
PROT_WRITE, MAP_SHARED, test.flash.fd, 0);
assert(map != MAP_FAILED);
memcpy(map, data, len);
munmap(map, test.context.backend.flash_size);
return 0;
}
char *get_dev_mtd(void)
{
return strdup(test.flash.path);
}