// SPDX-License-Identifier: GPL-2.0 /* * Arm Statistical Profiling Extensions (SPE) support * Copyright (c) 2017-2018, Arm Ltd. */ #include #include #include #include #include #include #include "arm-spe-pkt-decoder.h" #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define le16_to_cpu bswap_16 #define le32_to_cpu bswap_32 #define le64_to_cpu bswap_64 #define memcpy_le64(d, s, n) do { \ memcpy((d), (s), (n)); \ *(d) = le64_to_cpu(*(d)); \ } while (0) #else #define le16_to_cpu #define le32_to_cpu #define le64_to_cpu #define memcpy_le64 memcpy #endif 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 = le16_to_cpu(*(uint16_t *)buf); break; case 4: packet->payload = le32_to_cpu(*(uint32_t *)buf); break; case 8: packet->payload = le64_to_cpu(*(uint64_t *)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_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"); 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_IS_OTHER_SVE_OP(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 { arm_spe_pkt_out_string(&err, &buf, &buf_len, "OTHER"); 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_IS_LDST_ATOMIC(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"); } switch (SPE_OP_PKT_LDST_SUBCLASS_GET(payload)) { case SPE_OP_PKT_LDST_SUBCLASS_SIMD_FP: arm_spe_pkt_out_string(&err, &buf, &buf_len, " SIMD-FP"); break; case SPE_OP_PKT_LDST_SUBCLASS_GP_REG: arm_spe_pkt_out_string(&err, &buf, &buf_len, " GP-REG"); break; case SPE_OP_PKT_LDST_SUBCLASS_UNSPEC_REG: arm_spe_pkt_out_string(&err, &buf, &buf_len, " UNSPEC-REG"); break; case SPE_OP_PKT_LDST_SUBCLASS_NV_SYSREG: arm_spe_pkt_out_string(&err, &buf, &buf_len, " NV-SYSREG"); break; default: break; } if (SPE_OP_PKT_IS_LDST_SVE(payload)) { /* 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"); } 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 (SPE_OP_PKT_IS_INDIRECT_BRANCH(payload)) arm_spe_pkt_out_string(&err, &buf, &buf_len, " IND"); 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; switch (idx) { case SPE_ADDR_PKT_HDR_INDEX_INS: case SPE_ADDR_PKT_HDR_INDEX_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 == 1) ? "TGT" : "PC", 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; }