| // SPDX-License-Identifier: GPL-2.0-only |
| |
| #include <linux/net.h> |
| #include <linux/netdevice.h> |
| #include <linux/netlink.h> |
| #include <linux/types.h> |
| #include <net/pkt_sched.h> |
| |
| #include "sch_mqprio_lib.h" |
| |
| /* Returns true if the intervals [a, b) and [c, d) overlap. */ |
| static bool intervals_overlap(int a, int b, int c, int d) |
| { |
| int left = max(a, c), right = min(b, d); |
| |
| return left < right; |
| } |
| |
| static int mqprio_validate_queue_counts(struct net_device *dev, |
| const struct tc_mqprio_qopt *qopt, |
| bool allow_overlapping_txqs, |
| struct netlink_ext_ack *extack) |
| { |
| int i, j; |
| |
| for (i = 0; i < qopt->num_tc; i++) { |
| unsigned int last = qopt->offset[i] + qopt->count[i]; |
| |
| if (!qopt->count[i]) { |
| NL_SET_ERR_MSG_FMT_MOD(extack, "No queues for TC %d", |
| i); |
| return -EINVAL; |
| } |
| |
| /* Verify the queue count is in tx range being equal to the |
| * real_num_tx_queues indicates the last queue is in use. |
| */ |
| if (qopt->offset[i] >= dev->real_num_tx_queues || |
| last > dev->real_num_tx_queues) { |
| NL_SET_ERR_MSG_FMT_MOD(extack, |
| "Queues %d:%d for TC %d exceed the %d TX queues available", |
| qopt->count[i], qopt->offset[i], |
| i, dev->real_num_tx_queues); |
| return -EINVAL; |
| } |
| |
| if (allow_overlapping_txqs) |
| continue; |
| |
| /* Verify that the offset and counts do not overlap */ |
| for (j = i + 1; j < qopt->num_tc; j++) { |
| if (intervals_overlap(qopt->offset[i], last, |
| qopt->offset[j], |
| qopt->offset[j] + |
| qopt->count[j])) { |
| NL_SET_ERR_MSG_FMT_MOD(extack, |
| "TC %d queues %d@%d overlap with TC %d queues %d@%d", |
| i, qopt->count[i], qopt->offset[i], |
| j, qopt->count[j], qopt->offset[j]); |
| return -EINVAL; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| int mqprio_validate_qopt(struct net_device *dev, struct tc_mqprio_qopt *qopt, |
| bool validate_queue_counts, |
| bool allow_overlapping_txqs, |
| struct netlink_ext_ack *extack) |
| { |
| int i, err; |
| |
| /* Verify num_tc is not out of max range */ |
| if (qopt->num_tc > TC_MAX_QUEUE) { |
| NL_SET_ERR_MSG(extack, |
| "Number of traffic classes is outside valid range"); |
| return -EINVAL; |
| } |
| |
| /* Verify priority mapping uses valid tcs */ |
| for (i = 0; i <= TC_BITMASK; i++) { |
| if (qopt->prio_tc_map[i] >= qopt->num_tc) { |
| NL_SET_ERR_MSG(extack, |
| "Invalid traffic class in priority to traffic class mapping"); |
| return -EINVAL; |
| } |
| } |
| |
| if (validate_queue_counts) { |
| err = mqprio_validate_queue_counts(dev, qopt, |
| allow_overlapping_txqs, |
| extack); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(mqprio_validate_qopt); |
| |
| void mqprio_qopt_reconstruct(struct net_device *dev, struct tc_mqprio_qopt *qopt) |
| { |
| int tc, num_tc = netdev_get_num_tc(dev); |
| |
| qopt->num_tc = num_tc; |
| memcpy(qopt->prio_tc_map, dev->prio_tc_map, sizeof(qopt->prio_tc_map)); |
| |
| for (tc = 0; tc < num_tc; tc++) { |
| qopt->count[tc] = dev->tc_to_txq[tc].count; |
| qopt->offset[tc] = dev->tc_to_txq[tc].offset; |
| } |
| } |
| EXPORT_SYMBOL_GPL(mqprio_qopt_reconstruct); |
| |
| void mqprio_fp_to_offload(u32 fp[TC_QOPT_MAX_QUEUE], |
| struct tc_mqprio_qopt_offload *mqprio) |
| { |
| unsigned long preemptible_tcs = 0; |
| int tc; |
| |
| for (tc = 0; tc < TC_QOPT_MAX_QUEUE; tc++) |
| if (fp[tc] == TC_FP_PREEMPTIBLE) |
| preemptible_tcs |= BIT(tc); |
| |
| mqprio->preemptible_tcs = preemptible_tcs; |
| } |
| EXPORT_SYMBOL_GPL(mqprio_fp_to_offload); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Shared mqprio qdisc code currently between taprio and mqprio"); |