Driver implementation of the Ring buffers used for TX Data path communication with the Infineon WLAN Device via the PCIe bus using a shared memory. Signed-off-by: Gokul Sivakumar --- .../net/wireless/infineon/inffmac/flowring.c | 492 ++++++++++++++++++ .../net/wireless/infineon/inffmac/flowring.h | 74 +++ 2 files changed, 566 insertions(+) create mode 100644 drivers/net/wireless/infineon/inffmac/flowring.c create mode 100644 drivers/net/wireless/infineon/inffmac/flowring.h diff --git a/drivers/net/wireless/infineon/inffmac/flowring.c b/drivers/net/wireless/infineon/inffmac/flowring.c new file mode 100644 index 000000000000..5724904e995c --- /dev/null +++ b/drivers/net/wireless/infineon/inffmac/flowring.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2014 Broadcom Corporation + * + * Copyright (c) 2025, Infineon Technologies AG, or an affiliate of Infineon Technologies AG. + * All rights reserved. + */ + +#include +#include +#include +#include "utils.h" + +#include "core.h" +#include "debug.h" +#include "bus.h" +#include "proto.h" +#include "flowring.h" +#include "msgbuf.h" +#include "common.h" + +#define INFF_FLOWRING_HIGH 1024 +#define INFF_FLOWRING_LOW (INFF_FLOWRING_HIGH - 256) +#define INFF_FLOWRING_INVALID_IFIDX 0xff + +#define INFF_FLOWRING_HASH_AP(da, fifo, ifidx) (da[5] * 2 + (fifo) + (ifidx) * 16) +#define INFF_FLOWRING_HASH_STA(fifo, ifidx) ((fifo) + (ifidx) * 16) + +static const u8 inff_flowring_prio2fifo[] = { + 0, + 1, + 1, + 0, + 2, + 2, + 3, + 3 +}; + +static const u8 ALLFFMAC[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +static bool +inff_flowring_is_tdls_mac(struct inff_flowring *flow, u8 mac[ETH_ALEN]) +{ + struct inff_flowring_tdls_entry *search; + + search = flow->tdls_entry; + + while (search) { + if (memcmp(search->mac, mac, ETH_ALEN) == 0) + return true; + search = search->next; + } + + return false; +} + +u32 inff_flowring_lookup(struct inff_flowring *flow, u8 da[ETH_ALEN], + u8 prio, u8 ifidx) +{ + struct inff_flowring_hash *hash; + u16 hash_idx; + u32 i; + bool found; + bool sta; + u8 fifo; + u8 *mac; + + fifo = inff_flowring_prio2fifo[prio]; + sta = (flow->addr_mode[ifidx] == ADDR_INDIRECT); + mac = da; + if (!sta && (is_multicast_ether_addr(da))) { + mac = (u8 *)ALLFFMAC; + fifo = 0; + } + if ((sta) && flow->tdls_active && + (inff_flowring_is_tdls_mac(flow, da))) { + sta = false; + } + hash_idx = sta ? INFF_FLOWRING_HASH_STA(fifo, ifidx) : + INFF_FLOWRING_HASH_AP(mac, fifo, ifidx); + hash_idx &= (INFF_FLOWRING_HASHSIZE - 1); + found = false; + hash = flow->hash; + for (i = 0; i < INFF_FLOWRING_HASHSIZE; i++) { + if ((sta || (memcmp(hash[hash_idx].mac, mac, ETH_ALEN) == 0)) && + hash[hash_idx].fifo == fifo && + hash[hash_idx].ifidx == ifidx) { + found = true; + break; + } + hash_idx++; + hash_idx &= (INFF_FLOWRING_HASHSIZE - 1); + } + if (found) + return hash[hash_idx].flowid; + + return INFF_FLOWRING_INVALID_ID; +} + +u32 inff_flowring_create(struct inff_flowring *flow, u8 da[ETH_ALEN], + u8 prio, u8 ifidx) +{ + struct inff_flowring_ring *ring; + struct inff_flowring_hash *hash; + u16 hash_idx; + u32 i; + bool found; + u8 fifo; + bool sta; + u8 *mac; + + fifo = inff_flowring_prio2fifo[prio]; + sta = (flow->addr_mode[ifidx] == ADDR_INDIRECT); + mac = da; + if (!sta && (is_multicast_ether_addr(da))) { + mac = (u8 *)ALLFFMAC; + fifo = 0; + } + if ((sta) && flow->tdls_active && + (inff_flowring_is_tdls_mac(flow, da))) { + sta = false; + } + hash_idx = sta ? INFF_FLOWRING_HASH_STA(fifo, ifidx) : + INFF_FLOWRING_HASH_AP(mac, fifo, ifidx); + hash_idx &= (INFF_FLOWRING_HASHSIZE - 1); + found = false; + hash = flow->hash; + for (i = 0; i < INFF_FLOWRING_HASHSIZE; i++) { + if (hash[hash_idx].ifidx == INFF_FLOWRING_INVALID_IFIDX && + (is_zero_ether_addr(hash[hash_idx].mac))) { + found = true; + break; + } + hash_idx++; + hash_idx &= (INFF_FLOWRING_HASHSIZE - 1); + } + if (found) { + for (i = 0; i < flow->nrofrings; i++) { + if (!flow->rings[i]) + break; + } + if (i == flow->nrofrings) + return -ENOMEM; + + ring = kzalloc(sizeof(*ring), GFP_ATOMIC); + if (!ring) + return -ENOMEM; + + memcpy(hash[hash_idx].mac, mac, ETH_ALEN); + hash[hash_idx].fifo = fifo; + hash[hash_idx].ifidx = ifidx; + hash[hash_idx].flowid = i; + + ring->hash_id = hash_idx; + ring->status = RING_CLOSED; + skb_queue_head_init(&ring->skblist); + flow->rings[i] = ring; + + return i; + } + return INFF_FLOWRING_INVALID_ID; +} + +u8 inff_flowring_tid(struct inff_flowring *flow, u16 flowid) +{ + struct inff_flowring_ring *ring; + + ring = flow->rings[flowid]; + + return flow->hash[ring->hash_id].fifo; +} + +static void inff_flowring_block(struct inff_flowring *flow, u16 flowid, + bool blocked) +{ + struct inff_flowring_ring *ring; + struct inff_bus *bus_if; + struct inff_pub *drvr; + struct inff_if *ifp; + bool currently_blocked; + int i; + u8 ifidx; + unsigned long flags; + + spin_lock_irqsave(&flow->block_lock, flags); + + ring = flow->rings[flowid]; + if (ring->blocked == blocked) { + spin_unlock_irqrestore(&flow->block_lock, flags); + return; + } + ifidx = inff_flowring_ifidx_get(flow, flowid); + + currently_blocked = false; + for (i = 0; i < flow->nrofrings; i++) { + if (flow->rings[i] && i != flowid) { + ring = flow->rings[i]; + if (ring->status == RING_OPEN && + (inff_flowring_ifidx_get(flow, i) == ifidx)) { + if (ring->blocked) { + currently_blocked = true; + break; + } + } + } + } + flow->rings[flowid]->blocked = blocked; + if (currently_blocked) { + spin_unlock_irqrestore(&flow->block_lock, flags); + return; + } + + bus_if = dev_get_drvdata(flow->dev); + drvr = bus_if->drvr; + ifp = inff_get_ifp(drvr, ifidx); + inff_txflowblock_if(ifp, INFF_NETIF_STOP_REASON_FLOW, blocked); + + spin_unlock_irqrestore(&flow->block_lock, flags); +} + +void inff_flowring_delete(struct inff_flowring *flow, u16 flowid) +{ + struct inff_bus *bus_if = dev_get_drvdata(flow->dev); + struct inff_flowring_ring *ring; + struct inff_if *ifp; + u16 hash_idx; + u8 ifidx; + struct sk_buff *skb; + + ring = flow->rings[flowid]; + if (!ring) + return; + + ifidx = inff_flowring_ifidx_get(flow, flowid); + ifp = inff_get_ifp(bus_if->drvr, ifidx); + + inff_flowring_block(flow, flowid, false); + hash_idx = ring->hash_id; + flow->hash[hash_idx].ifidx = INFF_FLOWRING_INVALID_IFIDX; + eth_zero_addr(flow->hash[hash_idx].mac); + flow->rings[flowid] = NULL; + + skb = skb_dequeue(&ring->skblist); + while (skb) { + inff_txfinalize(ifp, skb, false); + skb = skb_dequeue(&ring->skblist); + } + + kfree(ring); +} + +u32 inff_flowring_enqueue(struct inff_flowring *flow, u16 flowid, + struct sk_buff *skb) +{ + struct inff_flowring_ring *ring; + + ring = flow->rings[flowid]; + + skb_queue_tail(&ring->skblist, skb); + + if (!ring->blocked && + (skb_queue_len(&ring->skblist) > INFF_FLOWRING_HIGH)) { + inff_flowring_block(flow, flowid, true); + inff_dbg(MSGBUF, "Flowcontrol: BLOCK for ring %d\n", flowid); + /* To prevent (work around) possible race condition, check + * queue len again. It is also possible to use locking to + * protect, but that is undesirable for every enqueue and + * dequeue. This simple check will solve a possible race + * condition if it occurs. + */ + if (skb_queue_len(&ring->skblist) < INFF_FLOWRING_LOW) + inff_flowring_block(flow, flowid, false); + } + return skb_queue_len(&ring->skblist); +} + +struct sk_buff *inff_flowring_dequeue(struct inff_flowring *flow, u16 flowid) +{ + struct inff_flowring_ring *ring; + struct sk_buff *skb; + + ring = flow->rings[flowid]; + if (ring->status != RING_OPEN) + return NULL; + + skb = skb_dequeue(&ring->skblist); + + if (ring->blocked && + (skb_queue_len(&ring->skblist) < INFF_FLOWRING_LOW)) { + inff_flowring_block(flow, flowid, false); + inff_dbg(MSGBUF, "Flowcontrol: OPEN for ring %d\n", flowid); + } + + return skb; +} + +void inff_flowring_reinsert(struct inff_flowring *flow, u16 flowid, + struct sk_buff *skb) +{ + struct inff_flowring_ring *ring; + + ring = flow->rings[flowid]; + + skb_queue_head(&ring->skblist, skb); +} + +u32 inff_flowring_qlen(struct inff_flowring *flow, u16 flowid) +{ + struct inff_flowring_ring *ring; + + ring = flow->rings[flowid]; + if (!ring) + return 0; + + if (ring->status != RING_OPEN) + return 0; + + return skb_queue_len(&ring->skblist); +} + +void inff_flowring_open(struct inff_flowring *flow, u16 flowid) +{ + struct inff_flowring_ring *ring; + + ring = flow->rings[flowid]; + if (!ring) { + inff_err("Ring NULL, for flowid %d\n", flowid); + return; + } + + ring->status = RING_OPEN; +} + +u8 inff_flowring_ifidx_get(struct inff_flowring *flow, u16 flowid) +{ + struct inff_flowring_ring *ring; + u16 hash_idx; + + ring = flow->rings[flowid]; + hash_idx = ring->hash_id; + + return flow->hash[hash_idx].ifidx; +} + +struct inff_flowring *inff_flowring_attach(struct device *dev, u16 nrofrings) +{ + struct inff_flowring *flow; + u32 i; + + flow = kzalloc(sizeof(*flow), GFP_KERNEL); + if (flow) { + flow->dev = dev; + flow->nrofrings = nrofrings; + spin_lock_init(&flow->block_lock); + for (i = 0; i < ARRAY_SIZE(flow->addr_mode); i++) + flow->addr_mode[i] = ADDR_INDIRECT; + for (i = 0; i < ARRAY_SIZE(flow->hash); i++) + flow->hash[i].ifidx = INFF_FLOWRING_INVALID_IFIDX; + flow->rings = kcalloc(nrofrings, sizeof(*flow->rings), + GFP_KERNEL); + if (!flow->rings) { + kfree(flow); + flow = NULL; + } + } + + return flow; +} + +void inff_flowring_detach(struct inff_flowring *flow) +{ + struct inff_bus *bus_if = dev_get_drvdata(flow->dev); + struct inff_pub *drvr = bus_if->drvr; + struct inff_flowring_tdls_entry *search; + struct inff_flowring_tdls_entry *remove; + u16 flowid; + + for (flowid = 0; flowid < flow->nrofrings; flowid++) { + if (flow->rings[flowid]) + inff_msgbuf_delete_flowring(drvr, flowid); + } + + search = flow->tdls_entry; + while (search) { + remove = search; + search = search->next; + kfree(remove); + } + kfree(flow->rings); + kfree(flow); +} + +void inff_flowring_configure_addr_mode(struct inff_flowring *flow, int ifidx, + enum proto_addr_mode addr_mode) +{ + struct inff_bus *bus_if = dev_get_drvdata(flow->dev); + struct inff_pub *drvr = bus_if->drvr; + u32 i; + u16 flowid; + + if (flow->addr_mode[ifidx] != addr_mode) { + for (i = 0; i < ARRAY_SIZE(flow->hash); i++) { + if (flow->hash[i].ifidx == ifidx) { + flowid = flow->hash[i].flowid; + if (flow->rings[flowid]->status != RING_OPEN) + continue; + inff_msgbuf_delete_flowring(drvr, flowid); + } + } + flow->addr_mode[ifidx] = addr_mode; + } +} + +void inff_flowring_delete_peer(struct inff_flowring *flow, int ifidx, + u8 peer[ETH_ALEN]) +{ + struct inff_bus *bus_if = dev_get_drvdata(flow->dev); + struct inff_pub *drvr = bus_if->drvr; + struct inff_flowring_hash *hash; + struct inff_flowring_tdls_entry *prev; + struct inff_flowring_tdls_entry *search; + u32 i; + u16 flowid; + bool sta; + + sta = (flow->addr_mode[ifidx] == ADDR_INDIRECT); + + search = flow->tdls_entry; + prev = NULL; + while (search) { + if (memcmp(search->mac, peer, ETH_ALEN) == 0) { + sta = false; + break; + } + prev = search; + search = search->next; + } + + hash = flow->hash; + for (i = 0; i < INFF_FLOWRING_HASHSIZE; i++) { + if ((sta || (memcmp(hash[i].mac, peer, ETH_ALEN) == 0)) && + hash[i].ifidx == ifidx) { + flowid = flow->hash[i].flowid; + if (flow->rings[flowid]->status == RING_OPEN) + inff_msgbuf_delete_flowring(drvr, flowid); + } + } + + if (search) { + if (prev) + prev->next = search->next; + else + flow->tdls_entry = search->next; + kfree(search); + if (!flow->tdls_entry) + flow->tdls_active = false; + } +} + +void inff_flowring_add_tdls_peer(struct inff_flowring *flow, int ifidx, + u8 peer[ETH_ALEN]) +{ + struct inff_flowring_tdls_entry *tdls_entry; + struct inff_flowring_tdls_entry *search; + + tdls_entry = kzalloc(sizeof(*tdls_entry), GFP_ATOMIC); + if (!tdls_entry) + return; + + memcpy(tdls_entry->mac, peer, ETH_ALEN); + tdls_entry->next = NULL; + if (!flow->tdls_entry) { + flow->tdls_entry = tdls_entry; + } else { + search = flow->tdls_entry; + if (memcmp(search->mac, peer, ETH_ALEN) == 0) + goto free_entry; + while (search->next) { + search = search->next; + if (memcmp(search->mac, peer, ETH_ALEN) == 0) + goto free_entry; + } + search->next = tdls_entry; + } + + flow->tdls_active = true; + return; + +free_entry: + kfree(tdls_entry); +} diff --git a/drivers/net/wireless/infineon/inffmac/flowring.h b/drivers/net/wireless/infineon/inffmac/flowring.h new file mode 100644 index 000000000000..ad502aa123f1 --- /dev/null +++ b/drivers/net/wireless/infineon/inffmac/flowring.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: ISC */ +/* + * Copyright (c) 2014 Broadcom Corporation + * + * Copyright (c) 2025, Infineon Technologies AG, or an affiliate of Infineon Technologies AG. + * All rights reserved. + */ + +#ifndef INFF_FLOWRING_H +#define INFF_FLOWRING_H + +#define INFF_FLOWRING_HASHSIZE 512 /* has to be 2^x */ +#define INFF_FLOWRING_INVALID_ID 0xFFFFFFFF + +struct inff_flowring_hash { + u8 mac[ETH_ALEN]; + u8 fifo; + u8 ifidx; + u16 flowid; +}; + +enum ring_status { + RING_CLOSED, + RING_CLOSING, + RING_OPEN +}; + +struct inff_flowring_ring { + u16 hash_id; + bool blocked; + enum ring_status status; + struct sk_buff_head skblist; +}; + +struct inff_flowring_tdls_entry { + u8 mac[ETH_ALEN]; + struct inff_flowring_tdls_entry *next; +}; + +struct inff_flowring { + struct device *dev; + struct inff_flowring_hash hash[INFF_FLOWRING_HASHSIZE]; + struct inff_flowring_ring **rings; + spinlock_t block_lock; /* used to protect flow ring */ + enum proto_addr_mode addr_mode[INFF_MAX_IFS]; + u16 nrofrings; + bool tdls_active; + struct inff_flowring_tdls_entry *tdls_entry; +}; + +u32 inff_flowring_lookup(struct inff_flowring *flow, u8 da[ETH_ALEN], + u8 prio, u8 ifidx); +u32 inff_flowring_create(struct inff_flowring *flow, u8 da[ETH_ALEN], + u8 prio, u8 ifidx); +void inff_flowring_delete(struct inff_flowring *flow, u16 flowid); +void inff_flowring_open(struct inff_flowring *flow, u16 flowid); +u8 inff_flowring_tid(struct inff_flowring *flow, u16 flowid); +u32 inff_flowring_enqueue(struct inff_flowring *flow, u16 flowid, + struct sk_buff *skb); +struct sk_buff *inff_flowring_dequeue(struct inff_flowring *flow, u16 flowid); +void inff_flowring_reinsert(struct inff_flowring *flow, u16 flowid, + struct sk_buff *skb); +u32 inff_flowring_qlen(struct inff_flowring *flow, u16 flowid); +u8 inff_flowring_ifidx_get(struct inff_flowring *flow, u16 flowid); +struct inff_flowring *inff_flowring_attach(struct device *dev, u16 nrofrings); +void inff_flowring_detach(struct inff_flowring *flow); +void inff_flowring_configure_addr_mode(struct inff_flowring *flow, int ifidx, + enum proto_addr_mode addr_mode); +void inff_flowring_delete_peer(struct inff_flowring *flow, int ifidx, + u8 peer[ETH_ALEN]); +void inff_flowring_add_tdls_peer(struct inff_flowring *flow, int ifidx, + u8 peer[ETH_ALEN]); + +#endif /* INFF_FLOWRING_H */ -- 2.25.1