root/drivers/hwtracing/stm/p_sys-t.c
// SPDX-License-Identifier: GPL-2.0
/*
 * MIPI SyS-T framing protocol for STM devices.
 * Copyright (c) 2018, Intel Corporation.
 */

#include <linux/configfs.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uuid.h>
#include <linux/stm.h>
#include "stm.h"

enum sys_t_message_type {
        MIPI_SYST_TYPE_BUILD    = 0,
        MIPI_SYST_TYPE_SHORT32,
        MIPI_SYST_TYPE_STRING,
        MIPI_SYST_TYPE_CATALOG,
        MIPI_SYST_TYPE_RAW      = 6,
        MIPI_SYST_TYPE_SHORT64,
        MIPI_SYST_TYPE_CLOCK,
        MIPI_SYST_TYPE_SBD,
};

enum sys_t_message_severity {
        MIPI_SYST_SEVERITY_MAX  = 0,
        MIPI_SYST_SEVERITY_FATAL,
        MIPI_SYST_SEVERITY_ERROR,
        MIPI_SYST_SEVERITY_WARNING,
        MIPI_SYST_SEVERITY_INFO,
        MIPI_SYST_SEVERITY_USER1,
        MIPI_SYST_SEVERITY_USER2,
        MIPI_SYST_SEVERITY_DEBUG,
};

enum sys_t_message_build_subtype {
        MIPI_SYST_BUILD_ID_COMPACT32 = 0,
        MIPI_SYST_BUILD_ID_COMPACT64,
        MIPI_SYST_BUILD_ID_LONG,
};

enum sys_t_message_clock_subtype {
        MIPI_SYST_CLOCK_TRANSPORT_SYNC = 1,
};

enum sys_t_message_string_subtype {
        MIPI_SYST_STRING_GENERIC        = 1,
        MIPI_SYST_STRING_FUNCTIONENTER,
        MIPI_SYST_STRING_FUNCTIONEXIT,
        MIPI_SYST_STRING_INVALIDPARAM   = 5,
        MIPI_SYST_STRING_ASSERT         = 7,
        MIPI_SYST_STRING_PRINTF_32      = 11,
        MIPI_SYST_STRING_PRINTF_64      = 12,
};

/**
 * enum sys_t_message_sbd_subtype - SyS-T SBD message subtypes
 * @MIPI_SYST_SBD_ID32: SBD message with 32-bit message ID
 * @MIPI_SYST_SBD_ID64: SBD message with 64-bit message ID
 *
 * Structured Binary Data messages can send information of arbitrary length,
 * together with ID's that describe BLOB's content and layout.
 */
enum sys_t_message_sbd_subtype {
        MIPI_SYST_SBD_ID32 = 0,
        MIPI_SYST_SBD_ID64 = 1,
};

#define MIPI_SYST_TYPE(t)               ((u32)(MIPI_SYST_TYPE_ ## t))
#define MIPI_SYST_SEVERITY(s)           ((u32)(MIPI_SYST_SEVERITY_ ## s) << 4)
#define MIPI_SYST_OPT_LOC               BIT(8)
#define MIPI_SYST_OPT_LEN               BIT(9)
#define MIPI_SYST_OPT_CHK               BIT(10)
#define MIPI_SYST_OPT_TS                BIT(11)
#define MIPI_SYST_UNIT(u)               ((u32)(u) << 12)
#define MIPI_SYST_ORIGIN(o)             ((u32)(o) << 16)
#define MIPI_SYST_OPT_GUID              BIT(23)
#define MIPI_SYST_SUBTYPE(s)            ((u32)(MIPI_SYST_ ## s) << 24)
#define MIPI_SYST_UNITLARGE(u)          (MIPI_SYST_UNIT(u & 0xf) | \
                                         MIPI_SYST_ORIGIN(u >> 4))
#define MIPI_SYST_TYPES(t, s)           (MIPI_SYST_TYPE(t) | \
                                         MIPI_SYST_SUBTYPE(t ## _ ## s))

#define DATA_HEADER     (MIPI_SYST_TYPES(STRING, GENERIC)       | \
                         MIPI_SYST_SEVERITY(INFO)               | \
                         MIPI_SYST_OPT_GUID)

#define CLOCK_SYNC_HEADER       (MIPI_SYST_TYPES(CLOCK, TRANSPORT_SYNC) | \
                                 MIPI_SYST_SEVERITY(MAX))

/*
 * SyS-T and ftrace headers are compatible to an extent that ftrace event ID
 * and args can be treated as SyS-T SBD message with 64-bit ID and arguments
 * BLOB right behind the header without modification. Bits [16:63] coming
 * together with message ID from ftrace aren't used by SBD and must be zeroed.
 *
 *         0       15  16   23  24     31  32   39  40  63
 * ftrace: <event_id>  <flags>  <preempt>  <-pid->  <----> <args>
 * SBD:    <------- msg_id ------------------------------> <BLOB>
 */
#define SBD_HEADER (MIPI_SYST_TYPES(SBD, ID64) | \
                         MIPI_SYST_SEVERITY(INFO)               | \
                         MIPI_SYST_OPT_GUID)

struct sys_t_policy_node {
        uuid_t          uuid;
        bool            do_len;
        unsigned long   ts_interval;
        unsigned long   clocksync_interval;
};

struct sys_t_output {
        struct sys_t_policy_node        node;
        unsigned long   ts_jiffies;
        unsigned long   clocksync_jiffies;
};

static void sys_t_policy_node_init(void *priv)
{
        struct sys_t_policy_node *pn = priv;

        uuid_gen(&pn->uuid);
}

static int sys_t_output_open(void *priv, struct stm_output *output)
{
        struct sys_t_policy_node *pn = priv;
        struct sys_t_output *opriv;

        opriv = kzalloc_obj(*opriv, GFP_ATOMIC);
        if (!opriv)
                return -ENOMEM;

        memcpy(&opriv->node, pn, sizeof(opriv->node));
        output->pdrv_private = opriv;

        return 0;
}

static void sys_t_output_close(struct stm_output *output)
{
        kfree(output->pdrv_private);
}

static ssize_t sys_t_policy_uuid_show(struct config_item *item,
                                      char *page)
{
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);

        return sprintf(page, "%pU\n", &pn->uuid);
}

static ssize_t
sys_t_policy_uuid_store(struct config_item *item, const char *page,
                        size_t count)
{
        struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
        int ret;

        mutex_lock(mutexp);
        ret = uuid_parse(page, &pn->uuid);
        mutex_unlock(mutexp);

        return ret < 0 ? ret : count;
}

CONFIGFS_ATTR(sys_t_policy_, uuid);

static ssize_t sys_t_policy_do_len_show(struct config_item *item,
                                      char *page)
{
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);

        return sprintf(page, "%d\n", pn->do_len);
}

static ssize_t
sys_t_policy_do_len_store(struct config_item *item, const char *page,
                        size_t count)
{
        struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
        int ret;

        mutex_lock(mutexp);
        ret = kstrtobool(page, &pn->do_len);
        mutex_unlock(mutexp);

        return ret ? ret : count;
}

CONFIGFS_ATTR(sys_t_policy_, do_len);

static ssize_t sys_t_policy_ts_interval_show(struct config_item *item,
                                             char *page)
{
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);

        return sprintf(page, "%u\n", jiffies_to_msecs(pn->ts_interval));
}

static ssize_t
sys_t_policy_ts_interval_store(struct config_item *item, const char *page,
                               size_t count)
{
        struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
        unsigned int ms;
        int ret;

        mutex_lock(mutexp);
        ret = kstrtouint(page, 10, &ms);
        mutex_unlock(mutexp);

        if (!ret) {
                pn->ts_interval = msecs_to_jiffies(ms);
                return count;
        }

        return ret;
}

CONFIGFS_ATTR(sys_t_policy_, ts_interval);

static ssize_t sys_t_policy_clocksync_interval_show(struct config_item *item,
                                                    char *page)
{
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);

        return sprintf(page, "%u\n", jiffies_to_msecs(pn->clocksync_interval));
}

static ssize_t
sys_t_policy_clocksync_interval_store(struct config_item *item,
                                      const char *page, size_t count)
{
        struct mutex *mutexp = &item->ci_group->cg_subsys->su_mutex;
        struct sys_t_policy_node *pn = to_pdrv_policy_node(item);
        unsigned int ms;
        int ret;

        mutex_lock(mutexp);
        ret = kstrtouint(page, 10, &ms);
        mutex_unlock(mutexp);

        if (!ret) {
                pn->clocksync_interval = msecs_to_jiffies(ms);
                return count;
        }

        return ret;
}

CONFIGFS_ATTR(sys_t_policy_, clocksync_interval);

static struct configfs_attribute *sys_t_policy_attrs[] = {
        &sys_t_policy_attr_uuid,
        &sys_t_policy_attr_do_len,
        &sys_t_policy_attr_ts_interval,
        &sys_t_policy_attr_clocksync_interval,
        NULL,
};

static inline bool sys_t_need_ts(struct sys_t_output *op)
{
        if (op->node.ts_interval &&
            time_after(jiffies, op->ts_jiffies + op->node.ts_interval)) {
                op->ts_jiffies = jiffies;

                return true;
        }

        return false;
}

static bool sys_t_need_clock_sync(struct sys_t_output *op)
{
        if (op->node.clocksync_interval &&
            time_after(jiffies,
                       op->clocksync_jiffies + op->node.clocksync_interval)) {
                op->clocksync_jiffies = jiffies;

                return true;
        }

        return false;
}

static ssize_t
sys_t_clock_sync(struct stm_data *data, unsigned int m, unsigned int c)
{
        u32 header = CLOCK_SYNC_HEADER;
        const unsigned char nil = 0;
        u64 payload[2]; /* Clock value and frequency */
        ssize_t sz;

        sz = data->packet(data, m, c, STP_PACKET_DATA, STP_PACKET_TIMESTAMPED,
                          4, (u8 *)&header);
        if (sz <= 0)
                return sz;

        payload[0] = ktime_get_real_ns();
        payload[1] = NSEC_PER_SEC;
        sz = stm_data_write(data, m, c, false, &payload, sizeof(payload));
        if (sz <= 0)
                return sz;

        data->packet(data, m, c, STP_PACKET_FLAG, 0, 0, &nil);

        return sizeof(header) + sizeof(payload);
}

static inline u32 sys_t_header(struct stm_source_data *source)
{
        if (source && source->type == STM_FTRACE)
                return SBD_HEADER;
        return DATA_HEADER;
}

static ssize_t sys_t_write_data(struct stm_data *data,
                                struct stm_source_data *source,
                                unsigned int master, unsigned int channel,
                                bool ts_first, const void *buf, size_t count)
{
        ssize_t sz;
        const unsigned char nil = 0;

        /*
         * Ftrace is zero-copy compatible with SyS-T SBD, but requires
         * special handling of first 64 bits. Trim and send them separately
         * to avoid damage on original ftrace buffer.
         */
        if (source && source->type == STM_FTRACE) {
                u64 compat_ftrace_header;
                ssize_t header_sz;
                ssize_t buf_sz;

                if (count < sizeof(compat_ftrace_header))
                        return -EINVAL;

                /* SBD only makes use of low 16 bits (event ID) from ftrace event */
                compat_ftrace_header = *(u64 *)buf & 0xffff;
                header_sz = stm_data_write(data, master, channel, false,
                                           &compat_ftrace_header,
                                           sizeof(compat_ftrace_header));
                if (header_sz != sizeof(compat_ftrace_header))
                        return header_sz;

                buf_sz = stm_data_write(data, master, channel, false,
                                        buf + header_sz, count - header_sz);
                if (buf_sz != count - header_sz)
                        return buf_sz;
                sz = header_sz + buf_sz;
        } else {
                sz = stm_data_write(data, master, channel, false, buf, count);
        }

        if (sz <= 0)
                return sz;

        data->packet(data, master, channel, STP_PACKET_FLAG, 0, 0, &nil);

        return sz;
}

static ssize_t sys_t_write(struct stm_data *data, struct stm_output *output,
                           unsigned int chan, const char *buf, size_t count,
                           struct stm_source_data *source)
{
        struct sys_t_output *op = output->pdrv_private;
        unsigned int c = output->channel + chan;
        unsigned int m = output->master;
        u32 header = sys_t_header(source);
        u8 uuid[UUID_SIZE];
        ssize_t sz;

        /* We require an existing policy node to proceed */
        if (!op)
                return -EINVAL;

        if (sys_t_need_clock_sync(op)) {
                sz = sys_t_clock_sync(data, m, c);
                if (sz <= 0)
                        return sz;
        }

        if (op->node.do_len)
                header |= MIPI_SYST_OPT_LEN;
        if (sys_t_need_ts(op))
                header |= MIPI_SYST_OPT_TS;

        /*
         * STP framing rules for SyS-T frames:
         *   * the first packet of the SyS-T frame is timestamped;
         *   * the last packet is a FLAG.
         */
        /* Message layout: HEADER / GUID / [LENGTH /][TIMESTAMP /] DATA */
        /* HEADER */
        sz = data->packet(data, m, c, STP_PACKET_DATA, STP_PACKET_TIMESTAMPED,
                          4, (u8 *)&header);
        if (sz <= 0)
                return sz;

        /* GUID */
        export_uuid(uuid, &op->node.uuid);
        sz = stm_data_write(data, m, c, false, uuid, sizeof(op->node.uuid));
        if (sz <= 0)
                return sz;

        /* [LENGTH] */
        if (op->node.do_len) {
                u16 length = count;

                sz = data->packet(data, m, c, STP_PACKET_DATA, 0, 2,
                                  (u8 *)&length);
                if (sz <= 0)
                        return sz;
        }

        /* [TIMESTAMP] */
        if (header & MIPI_SYST_OPT_TS) {
                u64 ts = ktime_get_real_ns();

                sz = stm_data_write(data, m, c, false, &ts, sizeof(ts));
                if (sz <= 0)
                        return sz;
        }

        /* DATA */
        return sys_t_write_data(data, source, m, c, false, buf, count);
}

static const struct stm_protocol_driver sys_t_pdrv = {
        .owner                  = THIS_MODULE,
        .name                   = "p_sys-t",
        .priv_sz                = sizeof(struct sys_t_policy_node),
        .write                  = sys_t_write,
        .policy_attr            = sys_t_policy_attrs,
        .policy_node_init       = sys_t_policy_node_init,
        .output_open            = sys_t_output_open,
        .output_close           = sys_t_output_close,
};

static int sys_t_stm_init(void)
{
        return stm_register_protocol(&sys_t_pdrv);
}

static void sys_t_stm_exit(void)
{
        stm_unregister_protocol(&sys_t_pdrv);
}

module_init(sys_t_stm_init);
module_exit(sys_t_stm_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MIPI SyS-T STM framing protocol driver");
MODULE_AUTHOR("Alexander Shishkin <alexander.shishkin@linux.intel.com>");