// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021 Gerhard Engleder */ #include "tsnep.h" #include /* 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; } }