new file mode 100644
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains netdevice communication.
+ *
+ * Copyright (C) [2022-2025] Renesas Electronics Corporation and/or its affiliates.
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <net/cfg80211.h>
+
+#include "core.h"
+#include "cfg80211.h"
+#include "dev.h"
+#include "params.h"
+#include "dbg.h"
+
+static int ra6w_dev_ndo_open(struct net_device *ndev)
+{
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_priv *priv = vif->priv;
+
+ vif->up = true;
+ priv->vif_started++;
+
+ if (ra6w_recovery_reprobe_get()) {
+ ra6w_recovery_reprobe_set(false);
+ return 0;
+ }
+
+ netif_carrier_off(ndev);
+
+ return 0;
+}
+
+static int ra6w_dev_ndo_close(struct net_device *ndev)
+{
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+ struct ra6w_cfg80211_priv *priv = NULL;
+
+ if (!vif)
+ return 0;
+
+ priv = vif->priv;
+ if (!priv)
+ return 0;
+
+ if (priv->scan_request) {
+ ra6w_ctrl_scan_cancel(&priv->core->ctrl, vif);
+ ra6w_cfg80211_scan_done(priv);
+ }
+
+ vif->up = false;
+ if (netif_carrier_ok(ndev)) {
+ if (vif->type == NL80211_IFTYPE_STATION ||
+ vif->type == NL80211_IFTYPE_P2P_CLIENT)
+ cfg80211_disconnected(ndev, WLAN_REASON_DEAUTH_LEAVING,
+ NULL, 0, true, GFP_ATOMIC);
+
+ netif_carrier_off(ndev);
+ }
+
+ if (vif->type == NL80211_IFTYPE_MONITOR)
+ priv->mon_vif_idx = RA6W_CFG80211_VIF_IDX_INVALID;
+
+ priv->vif_started--;
+
+ return 0;
+}
+
+static struct ra6w_cfg80211_sta *ra6w_dev_get_sta(struct ra6w_cfg80211_vif *vif,
+ struct sk_buff *skb)
+{
+ struct ra6w_cfg80211_sta *sta = NULL;
+
+ if (!vif)
+ return NULL;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ sta = vif->sta.ap;
+ if (sta && sta->valid)
+ return sta;
+
+ break;
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO: {
+ const struct ethhdr *eth = (struct ethhdr *)skb->data;
+
+ if (is_multicast_ether_addr(eth->h_dest)) {
+ sta = ra6w_cfg80211_sta_get(vif->priv, vif->ap.bcmc_index);
+ if (sta && sta->valid)
+ return sta;
+
+ break;
+ }
+
+ list_for_each_entry(sta, &vif->ap.sta_list, list) {
+ if (sta->valid && ether_addr_equal(sta->mac_addr, eth->h_dest))
+ return sta;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static netdev_tx_t ra6w_dev_tx(struct ra6w_cfg80211_vif *vif, struct sk_buff *skb)
+{
+ struct ra6w_cfg80211_priv *priv = vif->priv;
+ struct ra6w_tx *tx = &priv->core->tx;
+ struct ra6w_tx_buf *tx_buf = NULL;
+ const struct ra6w_cfg80211_sta *sta = NULL;
+ u8 hdr_size = RA6W_GET_DATA_SIZE(RA6W_TX_EXT_LEN, 0);
+ u8 tx_buf_ac = RA6W_TX_DATA_AC;
+ u8 sta_idx = RA6W_CFG80211_STA_IDX_INVALID;
+ u8 prio = skb->priority;
+
+ if (skb->len - hdr_size > RA6W_CMD_DATA_SIZE)
+ return -EINVAL;
+
+ sta = ra6w_dev_get_sta(vif, skb);
+ if (sta)
+ sta_idx = sta->sta_idx;
+
+ if (sta_idx == RA6W_CFG80211_STA_IDX_INVALID)
+ return -ENXIO;
+
+ if (skb_headroom(skb) < hdr_size) {
+ int ret;
+
+ ret = pskb_expand_head(skb, hdr_size, 0, GFP_ATOMIC);
+ if (ret < 0) {
+ ra6w_err("[%s] SKB resize failed: hdr_size %u (reserved %u) ret %d\n",
+ __func__, hdr_size, skb_headroom(skb), ret);
+
+ return -EFAULT;
+ }
+ }
+
+ if (skb->priority == 0 || skb->priority > IEEE80211_QOS_CTL_TAG1D_MASK)
+ prio = cfg80211_classify8021d(skb, NULL);
+
+ tx_buf = (struct ra6w_tx_buf *)skb_push(skb, hdr_size);
+ tx_buf->cmd = RA6W_CMD_DATA_TX;
+ tx_buf->ext_len = RA6W_TX_EXT_LEN;
+ tx_buf->ext_hdr.buf_idx = tx_buf_ac;
+ tx_buf->ext_hdr.tid = prio;
+ tx_buf->ext_hdr.vif_idx = vif->vif_idx;
+ tx_buf->ext_hdr.sta_idx = sta_idx;
+ tx_buf->ext_hdr.flags = 0;
+ tx_buf->ext_hdr.sn = 0;
+ tx_buf->data_len = cpu_to_le16(skb->len - hdr_size);
+
+ return ra6w_tx_event_post(tx, tx_buf_ac, skb);
+}
+
+static netdev_tx_t ra6w_dev_ndo_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ int ret;
+ struct ra6w_cfg80211_vif *vif = netdev_priv(ndev);
+
+ ret = ra6w_dev_tx(vif, skb);
+ if (ret) {
+ dev_kfree_skb(skb);
+ ndev->stats.tx_errors++;
+ ndev->stats.tx_dropped++;
+
+ return NETDEV_TX_OK;
+ }
+
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += skb->len;
+
+ return NETDEV_TX_OK;
+}
+
+static const struct net_device_ops ra6w_dev_ops = {
+ .ndo_open = ra6w_dev_ndo_open,
+ .ndo_stop = ra6w_dev_ndo_close,
+ .ndo_start_xmit = ra6w_dev_ndo_start_xmit,
+ .ndo_set_mac_address = eth_mac_addr,
+};
+
+static const struct net_device_ops ra6w_dev_monitor_ops = {
+ .ndo_open = ra6w_dev_ndo_open,
+ .ndo_stop = ra6w_dev_ndo_close,
+ .ndo_set_mac_address = eth_mac_addr,
+};
+
+static u32 ra6w_dev_ethtool_get_link(struct net_device *ndev)
+{
+ return netif_carrier_ok(ndev);
+}
+
+static const struct ethtool_ops ra6w_ethtool_ops = {
+ .get_link = ra6w_dev_ethtool_get_link,
+ .get_drvinfo = cfg80211_get_drvinfo,
+};
+
+void ra6w_dev_init(struct net_device *ndev)
+{
+ if (!ndev)
+ return;
+
+ ether_setup(ndev);
+
+ ndev->priv_flags &= ~IFF_TX_SKB_SHARING;
+
+ ra6w_dev_set_ops(ndev);
+ ndev->ethtool_ops = &ra6w_ethtool_ops;
+
+ ndev->needs_free_netdev = true;
+ ndev->watchdog_timeo = RA6W_CMD_TX_LIFETIME_MS;
+ ndev->needed_headroom += RA6W_GET_DATA_SIZE(RA6W_TX_EXT_LEN, 0);
+ ndev->hw_features = 0;
+}
+
+void ra6w_dev_set_ops(struct net_device *ndev)
+{
+ ndev->netdev_ops = &ra6w_dev_ops;
+ ndev->tx_queue_len = RA6W_TX_BUF_Q_MAX;
+}
+
+void ra6w_dev_set_monitor_ops(struct net_device *ndev)
+{
+ ndev->netdev_ops = &ra6w_dev_monitor_ops;
+}