root/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Arm Statistical Profiling Extensions (SPE) support
 * Copyright (c) 2017-2018, Arm Ltd.
 */

#include <stdio.h>
#include <string.h>
#include <endian.h>
#include <byteswap.h>
#include <linux/bitops.h>
#include <stdarg.h>
#include <linux/kernel.h>
#include <linux/unaligned.h>

#include "arm-spe-pkt-decoder.h"

static const char * const arm_spe_packet_name[] = {
        [ARM_SPE_PAD]           = "PAD",
        [ARM_SPE_END]           = "END",
        [ARM_SPE_TIMESTAMP]     = "TS",
        [ARM_SPE_ADDRESS]       = "ADDR",
        [ARM_SPE_COUNTER]       = "LAT",
        [ARM_SPE_CONTEXT]       = "CONTEXT",
        [ARM_SPE_OP_TYPE]       = "OP-TYPE",
        [ARM_SPE_EVENTS]        = "EVENTS",
        [ARM_SPE_DATA_SOURCE]   = "DATA-SOURCE",
};

const char *arm_spe_pkt_name(enum arm_spe_pkt_type type)
{
        return arm_spe_packet_name[type];
}

/*
 * Extracts the field "sz" from header bits and converts to bytes:
 *   00 : byte (1)
 *   01 : halfword (2)
 *   10 : word (4)
 *   11 : doubleword (8)
 */
static unsigned int arm_spe_payload_len(unsigned char hdr)
{
        return 1U << ((hdr & GENMASK_ULL(5, 4)) >> 4);
}

static int arm_spe_get_payload(const unsigned char *buf, size_t len,
                               unsigned char ext_hdr,
                               struct arm_spe_pkt *packet)
{
        size_t payload_len = arm_spe_payload_len(buf[ext_hdr]);

        if (len < 1 + ext_hdr + payload_len)
                return ARM_SPE_NEED_MORE_BYTES;

        buf += 1 + ext_hdr;

        switch (payload_len) {
        case 1: packet->payload = *(uint8_t *)buf; break;
        case 2: packet->payload = get_unaligned_le16(buf); break;
        case 4: packet->payload = get_unaligned_le32(buf); break;
        case 8: packet->payload = get_unaligned_le64(buf); break;
        default: return ARM_SPE_BAD_PACKET;
        }

        return 1 + ext_hdr + payload_len;
}

static int arm_spe_get_pad(struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_PAD;
        return 1;
}

static int arm_spe_get_alignment(const unsigned char *buf, size_t len,
                                 struct arm_spe_pkt *packet)
{
        unsigned int alignment = 1 << ((buf[0] & 0xf) + 1);

        if (len < alignment)
                return ARM_SPE_NEED_MORE_BYTES;

        packet->type = ARM_SPE_PAD;
        return alignment - (((uintptr_t)buf) & (alignment - 1));
}

static int arm_spe_get_end(struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_END;
        return 1;
}

static int arm_spe_get_timestamp(const unsigned char *buf, size_t len,
                                 struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_TIMESTAMP;
        return arm_spe_get_payload(buf, len, 0, packet);
}

static int arm_spe_get_events(const unsigned char *buf, size_t len,
                              struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_EVENTS;

        /* we use index to identify Events with a less number of
         * comparisons in arm_spe_pkt_desc(): E.g., the LLC-ACCESS,
         * LLC-REFILL, and REMOTE-ACCESS events are identified if
         * index > 1.
         */
        packet->index = arm_spe_payload_len(buf[0]);

        return arm_spe_get_payload(buf, len, 0, packet);
}

static int arm_spe_get_data_source(const unsigned char *buf, size_t len,
                                   struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_DATA_SOURCE;
        return arm_spe_get_payload(buf, len, 0, packet);
}

static int arm_spe_get_context(const unsigned char *buf, size_t len,
                               struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_CONTEXT;
        packet->index = SPE_CTX_PKT_HDR_INDEX(buf[0]);
        return arm_spe_get_payload(buf, len, 0, packet);
}

static int arm_spe_get_op_type(const unsigned char *buf, size_t len,
                               struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_OP_TYPE;
        packet->index = SPE_OP_PKT_HDR_CLASS(buf[0]);
        return arm_spe_get_payload(buf, len, 0, packet);
}

static int arm_spe_get_counter(const unsigned char *buf, size_t len,
                               const unsigned char ext_hdr, struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_COUNTER;

        if (ext_hdr)
                packet->index = SPE_HDR_EXTENDED_INDEX(buf[0], buf[1]);
        else
                packet->index = SPE_HDR_SHORT_INDEX(buf[0]);

        return arm_spe_get_payload(buf, len, ext_hdr, packet);
}

static int arm_spe_get_addr(const unsigned char *buf, size_t len,
                            const unsigned char ext_hdr, struct arm_spe_pkt *packet)
{
        packet->type = ARM_SPE_ADDRESS;

        if (ext_hdr)
                packet->index = SPE_HDR_EXTENDED_INDEX(buf[0], buf[1]);
        else
                packet->index = SPE_HDR_SHORT_INDEX(buf[0]);

        return arm_spe_get_payload(buf, len, ext_hdr, packet);
}

static int arm_spe_do_get_packet(const unsigned char *buf, size_t len,
                                 struct arm_spe_pkt *packet)
{
        unsigned int hdr;
        unsigned char ext_hdr = 0;

        memset(packet, 0, sizeof(struct arm_spe_pkt));

        if (!len)
                return ARM_SPE_NEED_MORE_BYTES;

        hdr = buf[0];

        if (hdr == SPE_HEADER0_PAD)
                return arm_spe_get_pad(packet);

        if (hdr == SPE_HEADER0_END) /* no timestamp at end of record */
                return arm_spe_get_end(packet);

        if (hdr == SPE_HEADER0_TIMESTAMP)
                return arm_spe_get_timestamp(buf, len, packet);

        if ((hdr & SPE_HEADER0_MASK1) == SPE_HEADER0_EVENTS)
                return arm_spe_get_events(buf, len, packet);

        if ((hdr & SPE_HEADER0_MASK1) == SPE_HEADER0_SOURCE)
                return arm_spe_get_data_source(buf, len, packet);

        if ((hdr & SPE_HEADER0_MASK2) == SPE_HEADER0_CONTEXT)
                return arm_spe_get_context(buf, len, packet);

        if ((hdr & SPE_HEADER0_MASK2) == SPE_HEADER0_OP_TYPE)
                return arm_spe_get_op_type(buf, len, packet);

        if ((hdr & SPE_HEADER0_MASK2) == SPE_HEADER0_EXTENDED) {
                /* 16-bit extended format header */
                if (len == 1)
                        return ARM_SPE_BAD_PACKET;

                ext_hdr = 1;
                hdr = buf[1];
                if (hdr == SPE_HEADER1_ALIGNMENT)
                        return arm_spe_get_alignment(buf, len, packet);
        }

        /*
         * The short format header's byte 0 or the extended format header's
         * byte 1 has been assigned to 'hdr', which uses the same encoding for
         * address packet and counter packet, so don't need to distinguish if
         * it's short format or extended format and handle in once.
         */
        if ((hdr & SPE_HEADER0_MASK3) == SPE_HEADER0_ADDRESS)
                return arm_spe_get_addr(buf, len, ext_hdr, packet);

        if ((hdr & SPE_HEADER0_MASK3) == SPE_HEADER0_COUNTER)
                return arm_spe_get_counter(buf, len, ext_hdr, packet);

        return ARM_SPE_BAD_PACKET;
}

int arm_spe_get_packet(const unsigned char *buf, size_t len,
                       struct arm_spe_pkt *packet)
{
        int ret;

        ret = arm_spe_do_get_packet(buf, len, packet);
        /* put multiple consecutive PADs on the same line, up to
         * the fixed-width output format of 16 bytes per line.
         */
        if (ret > 0 && packet->type == ARM_SPE_PAD) {
                while (ret < 16 && len > (size_t)ret && !buf[ret])
                        ret += 1;
        }
        return ret;
}

static int arm_spe_pkt_out_string(int *err, char **buf_p, size_t *blen,
                                  const char *fmt, ...)
{
        va_list ap;
        int ret;

        /* Bail out if any error occurred */
        if (err && *err)
                return *err;

        va_start(ap, fmt);
        ret = vsnprintf(*buf_p, *blen, fmt, ap);
        va_end(ap);

        if (ret < 0) {
                if (err && !*err)
                        *err = ret;

        /*
         * A return value of *blen or more means that the output was
         * truncated and the buffer is overrun.
         */
        } else if ((size_t)ret >= *blen) {
                (*buf_p)[*blen - 1] = '\0';

                /*
                 * Set *err to 'ret' to avoid overflow if tries to
                 * fill this buffer sequentially.
                 */
                if (err && !*err)
                        *err = ret;
        } else {
                *buf_p += ret;
                *blen -= ret;
        }

        return ret;
}

static int arm_spe_pkt_desc_event(const struct arm_spe_pkt *packet,
                                  char *buf, size_t buf_len)
{
        u64 payload = packet->payload;
        int err = 0;

        arm_spe_pkt_out_string(&err, &buf, &buf_len, "EV");

        if (payload & BIT(EV_EXCEPTION_GEN))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " EXCEPTION-GEN");
        if (payload & BIT(EV_RETIRED))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " RETIRED");
        if (payload & BIT(EV_L1D_ACCESS))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " L1D-ACCESS");
        if (payload & BIT(EV_L1D_REFILL))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " L1D-REFILL");
        if (payload & BIT(EV_TLB_ACCESS))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " TLB-ACCESS");
        if (payload & BIT(EV_TLB_WALK))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " TLB-REFILL");
        if (payload & BIT(EV_NOT_TAKEN))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " NOT-TAKEN");
        if (payload & BIT(EV_MISPRED))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " MISPRED");
        if (payload & BIT(EV_LLC_ACCESS))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " LLC-ACCESS");
        if (payload & BIT(EV_LLC_MISS))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " LLC-REFILL");
        if (payload & BIT(EV_REMOTE_ACCESS))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " REMOTE-ACCESS");
        if (payload & BIT(EV_ALIGNMENT))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " ALIGNMENT");
        if (payload & BIT(EV_TRANSACTIONAL))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " TXN");
        if (payload & BIT(EV_PARTIAL_PREDICATE))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " SVE-PARTIAL-PRED");
        if (payload & BIT(EV_EMPTY_PREDICATE))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " SVE-EMPTY-PRED");
        if (payload & BIT(EV_L2D_ACCESS))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " L2D-ACCESS");
        if (payload & BIT(EV_L2D_MISS))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " L2D-MISS");
        if (payload & BIT(EV_CACHE_DATA_MODIFIED))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " HITM");
        if (payload & BIT(EV_RECENTLY_FETCHED))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " LFB");
        if (payload & BIT(EV_DATA_SNOOPED))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " SNOOPED");
        if (payload & BIT(EV_STREAMING_SVE_MODE))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " STREAMING-SVE");
        if (payload & BIT(EV_SMCU))
                arm_spe_pkt_out_string(&err, &buf, &buf_len, " SMCU");

        return err;
}

static int arm_spe_pkt_desc_op_type(const struct arm_spe_pkt *packet,
                                    char *buf, size_t buf_len)
{
        u64 payload = packet->payload;
        int err = 0;

        switch (packet->index) {
        case SPE_OP_PKT_HDR_CLASS_OTHER:
                if (SPE_OP_PKT_OTHER_SUBCLASS_SVE(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, "SVE-OTHER");

                        /* SVE effective vector length */
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " EVLEN %d",
                                               SPE_OP_PKG_SVE_EVL(payload));

                        if (payload & SPE_OP_PKT_SVE_FP)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " FP");
                        if (payload & SPE_OP_PKT_SVE_PRED)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " PRED");
                } else if (SPE_OP_PKT_OTHER_SUBCLASS_SME(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, "SME-OTHER");

                        /* SME effective vector length or tile size */
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " ETS %d",
                                               SPE_OP_PKG_SME_ETS(payload));

                        if (payload & SPE_OP_PKT_OTHER_FP)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " FP");
                } else if (SPE_OP_PKT_OTHER_SUBCLASS_OTHER(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, "OTHER");
                        if (payload & SPE_OP_PKT_OTHER_ASE)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " ASE");
                        if (payload & SPE_OP_PKT_OTHER_FP)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " FP");
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " %s",
                                               payload & SPE_OP_PKT_COND ?
                                               "COND-SELECT" : "INSN-OTHER");
                }
                break;
        case SPE_OP_PKT_HDR_CLASS_LD_ST_ATOMIC:
                arm_spe_pkt_out_string(&err, &buf, &buf_len,
                                       payload & 0x1 ? "ST" : "LD");

                if (SPE_OP_PKT_LDST_SUBCLASS_EXTENDED(payload)) {
                        if (payload & SPE_OP_PKT_AT)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " AT");
                        if (payload & SPE_OP_PKT_EXCL)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " EXCL");
                        if (payload & SPE_OP_PKT_AR)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " AR");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_SIMD_FP(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " SIMD-FP");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_GP_REG(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " GP-REG");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_UNSPEC_REG(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " UNSPEC-REG");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_NV_SYSREG(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " NV-SYSREG");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_MTE_TAG(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " MTE-TAG");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_MEMCPY(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " MEMCPY");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_MEMSET(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " MEMSET");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_SVE_SME_REG(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " SVE-SME-REG");

                        /* SVE effective vector length */
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " EVLEN %d",
                                               SPE_OP_PKG_SVE_EVL(payload));

                        if (payload & SPE_OP_PKT_SVE_PRED)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " PRED");
                        if (payload & SPE_OP_PKT_SVE_SG)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " SG");
                } else if (SPE_OP_PKT_LDST_SUBCLASS_GCS(payload)) {
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " GCS");
                        if (payload & SPE_OP_PKT_GCS_COMM)
                                arm_spe_pkt_out_string(&err, &buf, &buf_len, " COMM");
                }
                break;
        case SPE_OP_PKT_HDR_CLASS_BR_ERET:
                arm_spe_pkt_out_string(&err, &buf, &buf_len, "B");

                if (payload & SPE_OP_PKT_COND)
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " COND");
                if (payload & SPE_OP_PKT_INDIRECT_BRANCH)
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " IND");
                if (payload & SPE_OP_PKT_GCS)
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " GCS");
                if (SPE_OP_PKT_CR_BL(payload))
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " CR-BL");
                if (SPE_OP_PKT_CR_RET(payload))
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " CR-RET");
                if (SPE_OP_PKT_CR_NON_BL_RET(payload))
                        arm_spe_pkt_out_string(&err, &buf, &buf_len, " CR-NON-BL-RET");
                break;
        default:
                /* Unknown index */
                err = -1;
                break;
        }

        return err;
}

static int arm_spe_pkt_desc_addr(const struct arm_spe_pkt *packet,
                                 char *buf, size_t buf_len)
{
        int ns, el, idx = packet->index;
        int ch, pat;
        u64 payload = packet->payload;
        int err = 0;
        static const char *idx_name[] = {"PC", "TGT", "VA", "PA", "PBT"};

        switch (idx) {
        case SPE_ADDR_PKT_HDR_INDEX_INS:
        case SPE_ADDR_PKT_HDR_INDEX_BRANCH:
        case SPE_ADDR_PKT_HDR_INDEX_PREV_BRANCH:
                ns = !!SPE_ADDR_PKT_GET_NS(payload);
                el = SPE_ADDR_PKT_GET_EL(payload);
                payload = SPE_ADDR_PKT_ADDR_GET_BYTES_0_6(payload);
                arm_spe_pkt_out_string(&err, &buf, &buf_len,
                                "%s 0x%llx el%d ns=%d",
                                idx_name[idx], payload, el, ns);
                break;
        case SPE_ADDR_PKT_HDR_INDEX_DATA_VIRT:
                arm_spe_pkt_out_string(&err, &buf, &buf_len,
                                       "VA 0x%llx", payload);
                break;
        case SPE_ADDR_PKT_HDR_INDEX_DATA_PHYS:
                ns = !!SPE_ADDR_PKT_GET_NS(payload);
                ch = !!SPE_ADDR_PKT_GET_CH(payload);
                pat = SPE_ADDR_PKT_GET_PAT(payload);
                payload = SPE_ADDR_PKT_ADDR_GET_BYTES_0_6(payload);
                arm_spe_pkt_out_string(&err, &buf, &buf_len,
                                       "PA 0x%llx ns=%d ch=%d pat=%x",
                                       payload, ns, ch, pat);
                break;
        default:
                /* Unknown index */
                err = -1;
                break;
        }

        return err;
}

static int arm_spe_pkt_desc_counter(const struct arm_spe_pkt *packet,
                                    char *buf, size_t buf_len)
{
        u64 payload = packet->payload;
        const char *name = arm_spe_pkt_name(packet->type);
        int err = 0;

        arm_spe_pkt_out_string(&err, &buf, &buf_len, "%s %d ", name,
                               (unsigned short)payload);

        switch (packet->index) {
        case SPE_CNT_PKT_HDR_INDEX_TOTAL_LAT:
                arm_spe_pkt_out_string(&err, &buf, &buf_len, "TOT");
                break;
        case SPE_CNT_PKT_HDR_INDEX_ISSUE_LAT:
                arm_spe_pkt_out_string(&err, &buf, &buf_len, "ISSUE");
                break;
        case SPE_CNT_PKT_HDR_INDEX_TRANS_LAT:
                arm_spe_pkt_out_string(&err, &buf, &buf_len, "XLAT");
                break;
        default:
                break;
        }

        return err;
}

int arm_spe_pkt_desc(const struct arm_spe_pkt *packet, char *buf,
                     size_t buf_len)
{
        int idx = packet->index;
        unsigned long long payload = packet->payload;
        const char *name = arm_spe_pkt_name(packet->type);
        char *buf_orig = buf;
        size_t blen = buf_len;
        int err = 0;

        switch (packet->type) {
        case ARM_SPE_BAD:
        case ARM_SPE_PAD:
        case ARM_SPE_END:
                arm_spe_pkt_out_string(&err, &buf, &blen, "%s", name);
                break;
        case ARM_SPE_EVENTS:
                err = arm_spe_pkt_desc_event(packet, buf, buf_len);
                break;
        case ARM_SPE_OP_TYPE:
                err = arm_spe_pkt_desc_op_type(packet, buf, buf_len);
                break;
        case ARM_SPE_DATA_SOURCE:
        case ARM_SPE_TIMESTAMP:
                arm_spe_pkt_out_string(&err, &buf, &blen, "%s %lld", name, payload);
                break;
        case ARM_SPE_ADDRESS:
                err = arm_spe_pkt_desc_addr(packet, buf, buf_len);
                break;
        case ARM_SPE_CONTEXT:
                arm_spe_pkt_out_string(&err, &buf, &blen, "%s 0x%lx el%d",
                                       name, (unsigned long)payload, idx + 1);
                break;
        case ARM_SPE_COUNTER:
                err = arm_spe_pkt_desc_counter(packet, buf, buf_len);
                break;
        default:
                /* Unknown packet type */
                err = -1;
                break;
        }

        /* Output raw data if detect any error */
        if (err) {
                err = 0;
                arm_spe_pkt_out_string(&err, &buf_orig, &buf_len, "%s 0x%llx (%d)",
                                       name, payload, packet->index);
        }

        return err;
}