| // 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; |
| } |
| } |