Files
remotesyn/examples/zynq7000/SIM/c/remote-port-proto.c
2022-09-09 17:53:03 +02:00

514 lines
17 KiB
C

/*
* Remote-port protocol
*
* Copyright (c) 2013 Xilinx Inc
* Written by Edgar E. Iglesias <edgar.iglesias@xilinx.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef _DEFAULT_SOURCE
# define _DEFAULT_SOURCE
#endif
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <assert.h>
#include "remote-port-proto.h"
#undef MIN
#define MIN(x, y) (x < y ? x : y)
#if defined(__linux__)
# include <endian.h>
#elif defined(__FreeBSD__) || defined(__NetBSD__)
# include <sys/endian.h>
#elif defined(__OpenBSD__)
# include <sys/types.h>
# define be16toh(x) betoh16(x)
# define be32toh(x) betoh32(x)
# define be64toh(x) betoh64(x)
#elif defined(__WIN32)
/* We assume little endian. */
# define htobe64(x) _byteswap_uint64(x)
# define htobe32(x) _byteswap_ulong(x)
# define htobe16(x) _byteswap_ushort(x)
# define be64toh(x) _byteswap_uint64(x)
# define be32toh(x) _byteswap_ulong(x)
# define be16toh(x) _byteswap_ushort(x)
#endif
/* Fallback for ancient Linux systems. */
#ifndef htobe64
# include <byteswap.h>
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define htobe64(x) bswap_64(x)
# define htobe32(x) bswap_32(x)
# define htobe16(x) bswap_16(x)
# define be64toh(x) bswap_64(x)
# define be32toh(x) bswap_32(x)
# define be16toh(x) bswap_16(x)
# else
# define htobe64(x) x
# define htobe32(x) x
# define htobe16(x) x
# define be64toh(x) x
# define be32toh(x) x
# define be16toh(x) x
# endif
#endif
static const char *rp_cmd_names[RP_CMD_max + 1] = {
[RP_CMD_nop] = "nop",
[RP_CMD_hello] = "hello",
[RP_CMD_cfg] = "cfg",
[RP_CMD_read] = "read",
[RP_CMD_write] = "write",
[RP_CMD_interrupt] = "interrupt",
[RP_CMD_sync] = "sync",
[RP_CMD_ats_req] = "ats_request",
[RP_CMD_ats_inv] = "ats_invalidation",
};
const char *rp_cmd_to_string(enum rp_cmd cmd)
{
assert(cmd <= RP_CMD_max);
return rp_cmd_names[cmd];
}
int rp_decode_hdr(struct rp_pkt *pkt)
{
int used = 0;
pkt->hdr.cmd = be32toh(pkt->hdr.cmd);
pkt->hdr.len = be32toh(pkt->hdr.len);
pkt->hdr.id = be32toh(pkt->hdr.id);
pkt->hdr.flags = be32toh(pkt->hdr.flags);
pkt->hdr.dev = be32toh(pkt->hdr.dev);
used += sizeof pkt->hdr;
return used;
}
int rp_decode_payload(struct rp_pkt *pkt)
{
int used = 0;
/* Master_id has an odd decoding due to historical reasons. */
uint64_t master_id;
switch (pkt->hdr.cmd) {
case RP_CMD_hello:
assert(pkt->hdr.len >= sizeof pkt->hello.version);
pkt->hello.version.major = be16toh(pkt->hello.version.major);
pkt->hello.version.minor = be16toh(pkt->hello.version.minor);
used += sizeof pkt->hello.version;
if ((pkt->hdr.len - used) >= sizeof pkt->hello.caps) {
void *offset;
int i;
pkt->hello.caps.offset = be32toh(pkt->hello.caps.offset);
pkt->hello.caps.len = be16toh(pkt->hello.caps.len);
offset = (char *)pkt + pkt->hello.caps.offset;
for (i = 0; i < pkt->hello.caps.len; i++) {
uint32_t cap;
/* We don't know if offset is 32bit aligned so use
* memcpy to do the endian conversion. */
memcpy(&cap, offset + i * sizeof cap, sizeof cap);
cap = be32toh(cap);
memcpy(offset + i * sizeof cap, &cap, sizeof cap);
}
used += sizeof pkt->hello.caps;
} else {
pkt->hello.caps.offset = 0;
pkt->hello.caps.len = 0;
}
/* Consume everything ignoring additional headers we do not yet
* know about. */
used = pkt->hdr.len;
break;
case RP_CMD_write:
case RP_CMD_read:
assert(pkt->hdr.len >= sizeof pkt->busaccess - sizeof pkt->hdr);
pkt->busaccess.timestamp = be64toh(pkt->busaccess.timestamp);
pkt->busaccess.addr = be64toh(pkt->busaccess.addr);
pkt->busaccess.master_id = be16toh(pkt->busaccess.master_id);
pkt->busaccess.attributes = be64toh(pkt->busaccess.attributes);
pkt->busaccess.len = be32toh(pkt->busaccess.len);
pkt->busaccess.width = be32toh(pkt->busaccess.width);
pkt->busaccess.stream_width = be32toh(pkt->busaccess.stream_width);
master_id = be16toh(pkt->busaccess.master_id);
used += sizeof pkt->busaccess - sizeof pkt->hdr;
if (pkt->busaccess.attributes & RP_BUS_ATTR_EXT_BASE) {
struct rp_pkt_busaccess_ext_base *pext = &pkt->busaccess_ext_base;
assert(pkt->hdr.len >= sizeof *pext - sizeof pkt->hdr);
master_id |= (uint64_t)be16toh(pext->master_id_31_16) << 16;
master_id |= (uint64_t)be32toh(pext->master_id_63_32) << 32;
pext->data_offset = be32toh(pext->data_offset);
pext->next_offset = be32toh(pext->next_offset);
pext->byte_enable_offset = be32toh(pext->byte_enable_offset);
pext->byte_enable_len = be32toh(pext->byte_enable_len);
used += sizeof *pext - sizeof pkt->busaccess;
}
pkt->busaccess.master_id = master_id;
break;
case RP_CMD_interrupt:
pkt->interrupt.timestamp = be64toh(pkt->interrupt.timestamp);
pkt->interrupt.vector = be64toh(pkt->interrupt.vector);
pkt->interrupt.line = be32toh(pkt->interrupt.line);
pkt->interrupt.val = pkt->interrupt.val;
used += pkt->hdr.len;
break;
case RP_CMD_sync:
pkt->sync.timestamp = be64toh(pkt->interrupt.timestamp);
used += pkt->hdr.len;
break;
case RP_CMD_ats_req:
case RP_CMD_ats_inv:
pkt->ats.attributes = be64toh(pkt->ats.attributes);
pkt->ats.addr = be64toh(pkt->ats.addr);
pkt->ats.len = be64toh(pkt->ats.len);
pkt->ats.result = be32toh(pkt->ats.result);
break;
default:
break;
}
return used;
}
void rp_encode_hdr(struct rp_pkt_hdr *hdr, uint32_t cmd, uint32_t id,
uint32_t dev, uint32_t len, uint32_t flags)
{
hdr->cmd = htobe32(cmd);
hdr->len = htobe32(len);
hdr->id = htobe32(id);
hdr->dev = htobe32(dev);
hdr->flags = htobe32(flags);
}
size_t rp_encode_hello_caps(uint32_t id, uint32_t dev, struct rp_pkt_hello *pkt,
uint16_t version_major, uint16_t version_minor,
uint32_t *caps, uint32_t *caps_out,
uint32_t caps_len)
{
size_t psize = sizeof *pkt + sizeof caps[0] * caps_len;
unsigned int i;
rp_encode_hdr(&pkt->hdr, RP_CMD_hello, id, dev,
psize - sizeof pkt->hdr, 0);
pkt->version.major = htobe16(version_major);
pkt->version.minor = htobe16(version_minor);
/* Feature list is appeneded right after the hello packet. */
pkt->caps.offset = htobe32(sizeof *pkt);
pkt->caps.len = htobe16(caps_len);
for (i = 0; i < caps_len; i++) {
uint32_t cap;
cap = caps[i];
caps_out[i] = htobe32(cap);
}
return sizeof *pkt;
}
static void rp_encode_busaccess_common(struct rp_pkt_busaccess *pkt,
int64_t clk, uint16_t master_id,
uint64_t addr, uint64_t attr, uint32_t size,
uint32_t width, uint32_t stream_width)
{
pkt->timestamp = htobe64(clk);
pkt->master_id = htobe16(master_id);
pkt->addr = htobe64(addr);
pkt->attributes = htobe64(attr);
pkt->len = htobe32(size);
pkt->width = htobe32(width);
pkt->stream_width = htobe32(stream_width);
}
size_t rp_encode_read(uint32_t id, uint32_t dev,
struct rp_pkt_busaccess *pkt,
int64_t clk, uint16_t master_id,
uint64_t addr, uint64_t attr, uint32_t size,
uint32_t width, uint32_t stream_width)
{
rp_encode_hdr(&pkt->hdr, RP_CMD_read, id, dev,
sizeof *pkt - sizeof pkt->hdr, 0);
rp_encode_busaccess_common(pkt, clk, master_id, addr, attr,
size, width, stream_width);
return sizeof *pkt;
}
size_t rp_encode_read_resp(uint32_t id, uint32_t dev,
struct rp_pkt_busaccess *pkt,
int64_t clk, uint16_t master_id,
uint64_t addr, uint64_t attr, uint32_t size,
uint32_t width, uint32_t stream_width)
{
rp_encode_hdr(&pkt->hdr, RP_CMD_read, id, dev,
sizeof *pkt - sizeof pkt->hdr + size, RP_PKT_FLAGS_response);
rp_encode_busaccess_common(pkt, clk, master_id, addr, attr,
size, width, stream_width);
return sizeof *pkt + size;
}
size_t rp_encode_write(uint32_t id, uint32_t dev,
struct rp_pkt_busaccess *pkt,
int64_t clk, uint16_t master_id,
uint64_t addr, uint64_t attr, uint32_t size,
uint32_t width, uint32_t stream_width)
{
rp_encode_hdr(&pkt->hdr, RP_CMD_write, id, dev,
sizeof *pkt - sizeof pkt->hdr + size, 0);
rp_encode_busaccess_common(pkt, clk, master_id, addr, attr,
size, width, stream_width);
return sizeof *pkt;
}
size_t rp_encode_write_resp(uint32_t id, uint32_t dev,
struct rp_pkt_busaccess *pkt,
int64_t clk, uint16_t master_id,
uint64_t addr, uint64_t attr, uint32_t size,
uint32_t width, uint32_t stream_width)
{
rp_encode_hdr(&pkt->hdr, RP_CMD_write, id, dev,
sizeof *pkt - sizeof pkt->hdr, RP_PKT_FLAGS_response);
rp_encode_busaccess_common(pkt, clk, master_id, addr, attr,
size, width, stream_width);
return sizeof *pkt;
}
/* New API for extended header. */
size_t rp_encode_busaccess(struct rp_peer_state *peer,
struct rp_pkt_busaccess_ext_base *pkt,
struct rp_encode_busaccess_in *in) {
struct rp_pkt_busaccess *pkt_v4_0 = (void *) pkt;
uint32_t hsize = 0;
uint32_t ret_size = 0;
/* Allocate packet space. */
if (in->cmd == RP_CMD_write && !(in->flags & RP_PKT_FLAGS_response)) {
hsize = in->size;
}
if (in->cmd == RP_CMD_read && (in->flags & RP_PKT_FLAGS_response)) {
hsize = in->size;
ret_size = in->size;
}
/* If peer does not support the busaccess base extensions, use the
* old layout. For responses, what matters is if we're responding
* to a packet with the extensions.
*/
if (!peer->caps.busaccess_ext_base && !(in->attr & RP_BUS_ATTR_EXT_BASE)) {
/* Old layout. */
assert(in->master_id < UINT16_MAX);
rp_encode_hdr(&pkt->hdr, in->cmd, in->id, in->dev,
sizeof *pkt_v4_0 - sizeof pkt->hdr + hsize, in->flags);
rp_encode_busaccess_common(pkt_v4_0, in->clk, in->master_id,
in->addr, in->attr,
in->size, in->width, in->stream_width);
return sizeof *pkt_v4_0 + ret_size;
}
/* Encode the extended fields. */
pkt->master_id_31_16 = htobe16(in->master_id >> 16);
pkt->master_id_63_32 = htobe32(in->master_id >> 32);
/* We always put data right after the header. */
pkt->data_offset = htobe32(sizeof *pkt);
pkt->next_offset = 0;
pkt->byte_enable_offset = htobe32(sizeof *pkt + hsize);
pkt->byte_enable_len = htobe32(in->byte_enable_len);
hsize += in->byte_enable_len;
rp_encode_hdr(&pkt->hdr, in->cmd, in->id, in->dev,
sizeof *pkt - sizeof pkt->hdr + hsize, in->flags);
rp_encode_busaccess_common(pkt_v4_0, in->clk, in->master_id, in->addr,
in->attr | RP_BUS_ATTR_EXT_BASE,
in->size, in->width, in->stream_width);
return sizeof *pkt + ret_size;
}
size_t rp_encode_interrupt_f(uint32_t id, uint32_t dev,
struct rp_pkt_interrupt *pkt,
int64_t clk,
uint32_t line, uint64_t vector, uint8_t val,
uint32_t flags)
{
rp_encode_hdr(&pkt->hdr, RP_CMD_interrupt, id, dev,
sizeof *pkt - sizeof pkt->hdr, flags);
pkt->timestamp = htobe64(clk);
pkt->vector = htobe64(vector);
pkt->line = htobe32(line);
pkt->val = val;
return sizeof *pkt;
}
size_t rp_encode_interrupt(uint32_t id, uint32_t dev,
struct rp_pkt_interrupt *pkt,
int64_t clk,
uint32_t line, uint64_t vector, uint8_t val)
{
return rp_encode_interrupt_f(id, dev, pkt, clk, line, vector, val, 0);
}
static size_t rp_encode_ats_common(uint32_t cmd, uint32_t id, uint32_t dev,
struct rp_pkt_ats *pkt,
int64_t clk, uint64_t attr, uint64_t addr,
uint64_t len, uint64_t result, uint32_t flags)
{
rp_encode_hdr(&pkt->hdr, cmd, id, dev,
sizeof *pkt - sizeof pkt->hdr, flags);
pkt->timestamp = htobe64(clk);
pkt->attributes = htobe64(attr);
pkt->addr = htobe64(addr);
pkt->len = htobe64(len);
pkt->result = htobe32(result);
return sizeof *pkt;
}
size_t rp_encode_ats_req(uint32_t id, uint32_t dev,
struct rp_pkt_ats *pkt,
int64_t clk, uint64_t attr, uint64_t addr,
uint64_t len, uint64_t result, uint32_t flags)
{
return rp_encode_ats_common(RP_CMD_ats_req, id, dev,
pkt, clk, attr,
addr, len, result, flags);
}
size_t rp_encode_ats_inv(uint32_t id, uint32_t dev,
struct rp_pkt_ats *pkt,
int64_t clk, uint64_t attr, uint64_t addr,
uint64_t len, uint64_t result, uint32_t flags)
{
return rp_encode_ats_common(RP_CMD_ats_inv, id, dev,
pkt, clk, attr,
addr, len, result, flags);
}
static size_t rp_encode_sync_common(uint32_t id, uint32_t dev,
struct rp_pkt_sync *pkt,
int64_t clk, uint32_t flags)
{
rp_encode_hdr(&pkt->hdr, RP_CMD_sync, id, dev,
sizeof *pkt - sizeof pkt->hdr, flags);
pkt->timestamp = htobe64(clk);
return sizeof *pkt;
}
size_t rp_encode_sync(uint32_t id, uint32_t dev,
struct rp_pkt_sync *pkt,
int64_t clk)
{
return rp_encode_sync_common(id, dev, pkt, clk, 0);
}
size_t rp_encode_sync_resp(uint32_t id, uint32_t dev,
struct rp_pkt_sync *pkt,
int64_t clk)
{
return rp_encode_sync_common(id, dev, pkt, clk, RP_PKT_FLAGS_response);
}
void rp_process_caps(struct rp_peer_state *peer,
void *caps, size_t caps_len)
{
int i;
assert(peer->caps.busaccess_ext_base == false);
for (i = 0; i < caps_len; i++) {
uint32_t cap;
memcpy(&cap, caps + i * sizeof cap, sizeof cap);
switch (cap) {
case CAP_BUSACCESS_EXT_BASE:
peer->caps.busaccess_ext_base = true;
break;
case CAP_BUSACCESS_EXT_BYTE_EN:
peer->caps.busaccess_ext_byte_en = true;
break;
case CAP_WIRE_POSTED_UPDATES:
peer->caps.wire_posted_updates = true;
break;
case CAP_ATS:
peer->caps.ats = true;
break;
}
}
}
void rp_dpkt_alloc(RemotePortDynPkt *dpkt, size_t size)
{
if (dpkt->size < size) {
char *u8;
dpkt->pkt = realloc(dpkt->pkt, size);
u8 = (void *) dpkt->pkt;
memset(u8 + dpkt->size, 0, size - dpkt->size);
dpkt->size = size;
}
}
void rp_dpkt_swap(RemotePortDynPkt *a, RemotePortDynPkt *b)
{
struct rp_pkt *tmp_pkt;
size_t tmp_size;
tmp_pkt = a->pkt;
tmp_size = a->size;
a->pkt = b->pkt;
a->size = b->size;
b->pkt = tmp_pkt;
b->size = tmp_size;
}
bool rp_dpkt_is_valid(RemotePortDynPkt *dpkt)
{
return dpkt->size > 0 && dpkt->pkt->hdr.len;
}
void rp_dpkt_invalidate(RemotePortDynPkt *dpkt)
{
assert(rp_dpkt_is_valid(dpkt));
dpkt->pkt->hdr.len = 0;
}
inline void rp_dpkt_free(RemotePortDynPkt *dpkt)
{
dpkt->size = 0;
free(dpkt->pkt);
}