root/drivers/net/ethernet/engleder/tsnep_tc.c
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2021 Gerhard Engleder <gerhard@engleder-embedded.com> */

#include "tsnep.h"

#include <net/pkt_sched.h>

/* save one operation at the end for additional operation at list change */
#define TSNEP_MAX_GCL_NUM (TSNEP_GCL_COUNT - 1)

static int tsnep_validate_gcl(struct tc_taprio_qopt_offload *qopt)
{
        int i;
        u64 cycle_time;

        if (!qopt->cycle_time)
                return -ERANGE;
        if (qopt->num_entries > TSNEP_MAX_GCL_NUM)
                return -EINVAL;
        cycle_time = 0;
        for (i = 0; i < qopt->num_entries; i++) {
                if (qopt->entries[i].command != TC_TAPRIO_CMD_SET_GATES)
                        return -EINVAL;
                if (qopt->entries[i].gate_mask & ~TSNEP_GCL_MASK)
                        return -EINVAL;
                if (qopt->entries[i].interval < TSNEP_GCL_MIN_INTERVAL)
                        return -EINVAL;
                cycle_time += qopt->entries[i].interval;
        }
        if (qopt->cycle_time != cycle_time)
                return -EINVAL;
        if (qopt->cycle_time_extension >= qopt->cycle_time)
                return -EINVAL;

        return 0;
}

static void tsnep_write_gcl_operation(struct tsnep_gcl *gcl, int index,
                                      u32 properties, u32 interval, bool flush)
{
        void __iomem *addr = gcl->addr +
                             sizeof(struct tsnep_gcl_operation) * index;

        gcl->operation[index].properties = properties;
        gcl->operation[index].interval = interval;

        iowrite32(properties, addr);
        iowrite32(interval, addr + sizeof(u32));

        if (flush) {
                /* flush write with read access */
                ioread32(addr);
        }
}

static u64 tsnep_change_duration(struct tsnep_gcl *gcl, int index)
{
        u64 duration;
        int count;

        /* change needs to be triggered one or two operations before start of
         * new gate control list
         * - change is triggered at start of operation (minimum one operation)
         * - operation with adjusted interval is inserted on demand to exactly
         *   meet the start of the new gate control list (optional)
         *
         * additionally properties are read directly after start of previous
         * operation
         *
         * therefore, three operations needs to be considered for the limit
         */
        duration = 0;
        count = 3;
        while (count) {
                duration += gcl->operation[index].interval;

                index--;
                if (index < 0)
                        index = gcl->count - 1;

                count--;
        }

        return duration;
}

static void tsnep_write_gcl(struct tsnep_gcl *gcl,
                            struct tc_taprio_qopt_offload *qopt)
{
        int i;
        u32 properties;
        u64 extend;
        u64 cut;

        gcl->base_time = ktime_to_ns(qopt->base_time);
        gcl->cycle_time = qopt->cycle_time;
        gcl->cycle_time_extension = qopt->cycle_time_extension;

        for (i = 0; i < qopt->num_entries; i++) {
                properties = qopt->entries[i].gate_mask;
                if (i == (qopt->num_entries - 1))
                        properties |= TSNEP_GCL_LAST;

                tsnep_write_gcl_operation(gcl, i, properties,
                                          qopt->entries[i].interval, true);
        }
        gcl->count = qopt->num_entries;

        /* calculate change limit; i.e., the time needed between enable and
         * start of new gate control list
         */

        /* case 1: extend cycle time for change
         * - change duration of last operation
         * - cycle time extension
         */
        extend = tsnep_change_duration(gcl, gcl->count - 1);
        extend += gcl->cycle_time_extension;

        /* case 2: cut cycle time for change
         * - maximum change duration
         */
        cut = 0;
        for (i = 0; i < gcl->count; i++)
                cut = max(cut, tsnep_change_duration(gcl, i));

        /* use maximum, because the actual case (extend or cut) can be
         * determined only after limit is known (chicken-and-egg problem)
         */
        gcl->change_limit = max(extend, cut);
}

static u64 tsnep_gcl_start_after(struct tsnep_gcl *gcl, u64 limit)
{
        u64 start = gcl->base_time;
        u64 n;

        if (start <= limit) {
                n = div64_u64(limit - start, gcl->cycle_time);
                start += (n + 1) * gcl->cycle_time;
        }

        return start;
}

static u64 tsnep_gcl_start_before(struct tsnep_gcl *gcl, u64 limit)
{
        u64 start = gcl->base_time;
        u64 n;

        n = div64_u64(limit - start, gcl->cycle_time);
        start += n * gcl->cycle_time;
        if (start == limit)
                start -= gcl->cycle_time;

        return start;
}

static u64 tsnep_set_gcl_change(struct tsnep_gcl *gcl, int index, u64 change,
                                bool insert)
{
        /* previous operation triggers change and properties are evaluated at
         * start of operation
         */
        if (index == 0)
                index = gcl->count - 1;
        else
                index = index - 1;
        change -= gcl->operation[index].interval;

        /* optionally change to new list with additional operation in between */
        if (insert) {
                void __iomem *addr = gcl->addr +
                                     sizeof(struct tsnep_gcl_operation) * index;

                gcl->operation[index].properties |= TSNEP_GCL_INSERT;
                iowrite32(gcl->operation[index].properties, addr);
        }

        return change;
}

static void tsnep_clean_gcl(struct tsnep_gcl *gcl)
{
        int i;
        u32 mask = TSNEP_GCL_LAST | TSNEP_GCL_MASK;
        void __iomem *addr;

        /* search for insert operation and reset properties */
        for (i = 0; i < gcl->count; i++) {
                if (gcl->operation[i].properties & ~mask) {
                        addr = gcl->addr +
                               sizeof(struct tsnep_gcl_operation) * i;

                        gcl->operation[i].properties &= mask;
                        iowrite32(gcl->operation[i].properties, addr);

                        break;
                }
        }
}

static u64 tsnep_insert_gcl_operation(struct tsnep_gcl *gcl, int ref,
                                      u64 change, u32 interval)
{
        u32 properties;

        properties = gcl->operation[ref].properties & TSNEP_GCL_MASK;
        /* change to new list directly after inserted operation */
        properties |= TSNEP_GCL_CHANGE;

        /* last operation of list is reserved to insert operation */
        tsnep_write_gcl_operation(gcl, TSNEP_GCL_COUNT - 1, properties,
                                  interval, false);

        return tsnep_set_gcl_change(gcl, ref, change, true);
}

static u64 tsnep_extend_gcl(struct tsnep_gcl *gcl, u64 start, u32 extension)
{
        int ref = gcl->count - 1;
        u32 interval = gcl->operation[ref].interval + extension;

        start -= gcl->operation[ref].interval;

        return tsnep_insert_gcl_operation(gcl, ref, start, interval);
}

static u64 tsnep_cut_gcl(struct tsnep_gcl *gcl, u64 start, u64 cycle_time)
{
        u64 sum = 0;
        int i;

        /* find operation which shall be cutted */
        for (i = 0; i < gcl->count; i++) {
                u64 sum_tmp = sum + gcl->operation[i].interval;
                u64 interval;

                /* sum up operations as long as cycle time is not exceeded */
                if (sum_tmp > cycle_time)
                        break;

                /* remaining interval must be big enough for hardware */
                interval = cycle_time - sum_tmp;
                if (interval > 0 && interval < TSNEP_GCL_MIN_INTERVAL)
                        break;

                sum = sum_tmp;
        }
        if (sum == cycle_time) {
                /* no need to cut operation itself or whole cycle
                 * => change exactly at operation
                 */
                return tsnep_set_gcl_change(gcl, i, start + sum, false);
        }
        return tsnep_insert_gcl_operation(gcl, i, start + sum,
                                          cycle_time - sum);
}

static int tsnep_enable_gcl(struct tsnep_adapter *adapter,
                            struct tsnep_gcl *gcl, struct tsnep_gcl *curr)
{
        u64 system_time;
        u64 timeout;
        u64 limit;

        /* estimate timeout limit after timeout enable, actually timeout limit
         * in hardware will be earlier than estimate so we are on the safe side
         */
        tsnep_get_system_time(adapter, &system_time);
        timeout = system_time + TSNEP_GC_TIMEOUT;

        if (curr)
                limit = timeout + curr->change_limit;
        else
                limit = timeout;

        gcl->start_time = tsnep_gcl_start_after(gcl, limit);

        /* gate control time register is only 32bit => time shall be in the near
         * future (no driver support for far future implemented)
         */
        if ((gcl->start_time - system_time) >= U32_MAX)
                return -EAGAIN;

        if (curr) {
                /* change gate control list */
                u64 last;
                u64 change;

                last = tsnep_gcl_start_before(curr, gcl->start_time);
                if ((last + curr->cycle_time) == gcl->start_time)
                        change = tsnep_cut_gcl(curr, last,
                                               gcl->start_time - last);
                else if (((gcl->start_time - last) <=
                          curr->cycle_time_extension) ||
                         ((gcl->start_time - last) <= TSNEP_GCL_MIN_INTERVAL))
                        change = tsnep_extend_gcl(curr, last,
                                                  gcl->start_time - last);
                else
                        change = tsnep_cut_gcl(curr, last,
                                               gcl->start_time - last);

                WARN_ON(change <= timeout);
                gcl->change = true;
                iowrite32(change & 0xFFFFFFFF, adapter->addr + TSNEP_GC_CHANGE);
        } else {
                /* start gate control list */
                WARN_ON(gcl->start_time <= timeout);
                gcl->change = false;
                iowrite32(gcl->start_time & 0xFFFFFFFF,
                          adapter->addr + TSNEP_GC_TIME);
        }

        return 0;
}

static int tsnep_taprio(struct tsnep_adapter *adapter,
                        struct tc_taprio_qopt_offload *qopt)
{
        struct tsnep_gcl *gcl;
        struct tsnep_gcl *curr;
        int retval;

        if (!adapter->gate_control)
                return -EOPNOTSUPP;

        if (qopt->cmd == TAPRIO_CMD_DESTROY) {
                /* disable gate control if active */
                mutex_lock(&adapter->gate_control_lock);

                if (adapter->gate_control_active) {
                        iowrite8(TSNEP_GC_DISABLE, adapter->addr + TSNEP_GC);
                        adapter->gate_control_active = false;
                }

                mutex_unlock(&adapter->gate_control_lock);

                return 0;
        } else if (qopt->cmd != TAPRIO_CMD_REPLACE) {
                return -EOPNOTSUPP;
        }

        retval = tsnep_validate_gcl(qopt);
        if (retval)
                return retval;

        mutex_lock(&adapter->gate_control_lock);

        gcl = &adapter->gcl[adapter->next_gcl];
        tsnep_write_gcl(gcl, qopt);

        /* select current gate control list if active */
        if (adapter->gate_control_active) {
                if (adapter->next_gcl == 0)
                        curr = &adapter->gcl[1];
                else
                        curr = &adapter->gcl[0];
        } else {
                curr = NULL;
        }

        for (;;) {
                /* start timeout which discards late enable, this helps ensuring
                 * that start/change time are in the future at enable
                 */
                iowrite8(TSNEP_GC_ENABLE_TIMEOUT, adapter->addr + TSNEP_GC);

                retval = tsnep_enable_gcl(adapter, gcl, curr);
                if (retval) {
                        mutex_unlock(&adapter->gate_control_lock);

                        return retval;
                }

                /* enable gate control list */
                if (adapter->next_gcl == 0)
                        iowrite8(TSNEP_GC_ENABLE_A, adapter->addr + TSNEP_GC);
                else
                        iowrite8(TSNEP_GC_ENABLE_B, adapter->addr + TSNEP_GC);

                /* done if timeout did not happen */
                if (!(ioread32(adapter->addr + TSNEP_GC) &
                      TSNEP_GC_TIMEOUT_SIGNAL))
                        break;

                /* timeout is acknowledged with any enable */
                iowrite8(TSNEP_GC_ENABLE_A, adapter->addr + TSNEP_GC);

                if (curr)
                        tsnep_clean_gcl(curr);

                /* retry because of timeout */
        }

        adapter->gate_control_active = true;

        if (adapter->next_gcl == 0)
                adapter->next_gcl = 1;
        else
                adapter->next_gcl = 0;

        mutex_unlock(&adapter->gate_control_lock);

        return 0;
}

static int tsnep_tc_query_caps(struct tsnep_adapter *adapter,
                               struct tc_query_caps_base *base)
{
        switch (base->type) {
        case TC_SETUP_QDISC_TAPRIO: {
                struct tc_taprio_caps *caps = base->caps;

                if (!adapter->gate_control)
                        return -EOPNOTSUPP;

                caps->gate_mask_per_txq = true;

                return 0;
        }
        default:
                return -EOPNOTSUPP;
        }
}

int tsnep_tc_setup(struct net_device *netdev, enum tc_setup_type type,
                   void *type_data)
{
        struct tsnep_adapter *adapter = netdev_priv(netdev);

        switch (type) {
        case TC_QUERY_CAPS:
                return tsnep_tc_query_caps(adapter, type_data);
        case TC_SETUP_QDISC_TAPRIO:
                return tsnep_taprio(adapter, type_data);
        default:
                return -EOPNOTSUPP;
        }
}

int tsnep_tc_init(struct tsnep_adapter *adapter)
{
        if (!adapter->gate_control)
                return 0;

        /* open all gates */
        iowrite8(TSNEP_GC_DISABLE, adapter->addr + TSNEP_GC);
        iowrite32(TSNEP_GC_OPEN | TSNEP_GC_NEXT_OPEN, adapter->addr + TSNEP_GC);

        adapter->gcl[0].addr = adapter->addr + TSNEP_GCL_A;
        adapter->gcl[1].addr = adapter->addr + TSNEP_GCL_B;

        return 0;
}

void tsnep_tc_cleanup(struct tsnep_adapter *adapter)
{
        if (!adapter->gate_control)
                return;

        if (adapter->gate_control_active) {
                iowrite8(TSNEP_GC_DISABLE, adapter->addr + TSNEP_GC);
                adapter->gate_control_active = false;
        }
}