linux/net/ncsi/ncsi-aen.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright Gavin Shan, IBM Corporation 2016.
   4 */
   5
   6#include <linux/module.h>
   7#include <linux/kernel.h>
   8#include <linux/init.h>
   9#include <linux/netdevice.h>
  10#include <linux/skbuff.h>
  11
  12#include <net/ncsi.h>
  13#include <net/net_namespace.h>
  14#include <net/sock.h>
  15
  16#include "internal.h"
  17#include "ncsi-pkt.h"
  18
  19static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h,
  20                                 const unsigned short payload)
  21{
  22        u32 checksum;
  23        __be32 *pchecksum;
  24
  25        if (h->common.revision != NCSI_PKT_REVISION)
  26                return -EINVAL;
  27        if (ntohs(h->common.length) != payload)
  28                return -EINVAL;
  29
  30        /* Validate checksum, which might be zeroes if the
  31         * sender doesn't support checksum according to NCSI
  32         * specification.
  33         */
  34        pchecksum = (__be32 *)((void *)(h + 1) + payload - 4);
  35        if (ntohl(*pchecksum) == 0)
  36                return 0;
  37
  38        checksum = ncsi_calculate_checksum((unsigned char *)h,
  39                                           sizeof(*h) + payload - 4);
  40        if (*pchecksum != htonl(checksum))
  41                return -EINVAL;
  42
  43        return 0;
  44}
  45
  46static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp,
  47                                struct ncsi_aen_pkt_hdr *h)
  48{
  49        struct ncsi_channel *nc, *tmp;
  50        struct ncsi_channel_mode *ncm;
  51        unsigned long old_data, data;
  52        struct ncsi_aen_lsc_pkt *lsc;
  53        struct ncsi_package *np;
  54        bool had_link, has_link;
  55        unsigned long flags;
  56        bool chained;
  57        int state;
  58
  59        /* Find the NCSI channel */
  60        ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
  61        if (!nc)
  62                return -ENODEV;
  63
  64        /* Update the link status */
  65        lsc = (struct ncsi_aen_lsc_pkt *)h;
  66
  67        spin_lock_irqsave(&nc->lock, flags);
  68        ncm = &nc->modes[NCSI_MODE_LINK];
  69        old_data = ncm->data[2];
  70        data = ntohl(lsc->status);
  71        ncm->data[2] = data;
  72        ncm->data[4] = ntohl(lsc->oem_status);
  73
  74        had_link = !!(old_data & 0x1);
  75        has_link = !!(data & 0x1);
  76
  77        netdev_dbg(ndp->ndev.dev, "NCSI: LSC AEN - channel %u state %s\n",
  78                   nc->id, data & 0x1 ? "up" : "down");
  79
  80        chained = !list_empty(&nc->link);
  81        state = nc->state;
  82        spin_unlock_irqrestore(&nc->lock, flags);
  83
  84        if (state == NCSI_CHANNEL_INACTIVE)
  85                netdev_warn(ndp->ndev.dev,
  86                            "NCSI: Inactive channel %u received AEN!\n",
  87                            nc->id);
  88
  89        if ((had_link == has_link) || chained)
  90                return 0;
  91
  92        if (!ndp->multi_package && !nc->package->multi_channel) {
  93                if (had_link) {
  94                        ndp->flags |= NCSI_DEV_RESHUFFLE;
  95                        ncsi_stop_channel_monitor(nc);
  96                        spin_lock_irqsave(&ndp->lock, flags);
  97                        list_add_tail_rcu(&nc->link, &ndp->channel_queue);
  98                        spin_unlock_irqrestore(&ndp->lock, flags);
  99                        return ncsi_process_next_channel(ndp);
 100                }
 101                /* Configured channel came up */
 102                return 0;
 103        }
 104
 105        if (had_link) {
 106                ncm = &nc->modes[NCSI_MODE_TX_ENABLE];
 107                if (ncsi_channel_is_last(ndp, nc)) {
 108                        /* No channels left, reconfigure */
 109                        return ncsi_reset_dev(&ndp->ndev);
 110                } else if (ncm->enable) {
 111                        /* Need to failover Tx channel */
 112                        ncsi_update_tx_channel(ndp, nc->package, nc, NULL);
 113                }
 114        } else if (has_link && nc->package->preferred_channel == nc) {
 115                /* Return Tx to preferred channel */
 116                ncsi_update_tx_channel(ndp, nc->package, NULL, nc);
 117        } else if (has_link) {
 118                NCSI_FOR_EACH_PACKAGE(ndp, np) {
 119                        NCSI_FOR_EACH_CHANNEL(np, tmp) {
 120                                /* Enable Tx on this channel if the current Tx
 121                                 * channel is down.
 122                                 */
 123                                ncm = &tmp->modes[NCSI_MODE_TX_ENABLE];
 124                                if (ncm->enable &&
 125                                    !ncsi_channel_has_link(tmp)) {
 126                                        ncsi_update_tx_channel(ndp, nc->package,
 127                                                               tmp, nc);
 128                                        break;
 129                                }
 130                        }
 131                }
 132        }
 133
 134        /* Leave configured channels active in a multi-channel scenario so
 135         * AEN events are still received.
 136         */
 137        return 0;
 138}
 139
 140static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp,
 141                               struct ncsi_aen_pkt_hdr *h)
 142{
 143        struct ncsi_channel *nc;
 144        unsigned long flags;
 145
 146        /* Find the NCSI channel */
 147        ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
 148        if (!nc)
 149                return -ENODEV;
 150
 151        spin_lock_irqsave(&nc->lock, flags);
 152        if (!list_empty(&nc->link) ||
 153            nc->state != NCSI_CHANNEL_ACTIVE) {
 154                spin_unlock_irqrestore(&nc->lock, flags);
 155                return 0;
 156        }
 157        spin_unlock_irqrestore(&nc->lock, flags);
 158
 159        ncsi_stop_channel_monitor(nc);
 160        spin_lock_irqsave(&nc->lock, flags);
 161        nc->state = NCSI_CHANNEL_INVISIBLE;
 162        spin_unlock_irqrestore(&nc->lock, flags);
 163
 164        spin_lock_irqsave(&ndp->lock, flags);
 165        nc->state = NCSI_CHANNEL_INACTIVE;
 166        list_add_tail_rcu(&nc->link, &ndp->channel_queue);
 167        spin_unlock_irqrestore(&ndp->lock, flags);
 168
 169        return ncsi_process_next_channel(ndp);
 170}
 171
 172static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp,
 173                                   struct ncsi_aen_pkt_hdr *h)
 174{
 175        struct ncsi_channel *nc;
 176        struct ncsi_channel_mode *ncm;
 177        struct ncsi_aen_hncdsc_pkt *hncdsc;
 178        unsigned long flags;
 179
 180        /* Find the NCSI channel */
 181        ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
 182        if (!nc)
 183                return -ENODEV;
 184
 185        spin_lock_irqsave(&nc->lock, flags);
 186        ncm = &nc->modes[NCSI_MODE_LINK];
 187        hncdsc = (struct ncsi_aen_hncdsc_pkt *)h;
 188        ncm->data[3] = ntohl(hncdsc->status);
 189        spin_unlock_irqrestore(&nc->lock, flags);
 190        netdev_dbg(ndp->ndev.dev,
 191                   "NCSI: host driver %srunning on channel %u\n",
 192                   ncm->data[3] & 0x1 ? "" : "not ", nc->id);
 193
 194        return 0;
 195}
 196
 197static struct ncsi_aen_handler {
 198        unsigned char type;
 199        int           payload;
 200        int           (*handler)(struct ncsi_dev_priv *ndp,
 201                                 struct ncsi_aen_pkt_hdr *h);
 202} ncsi_aen_handlers[] = {
 203        { NCSI_PKT_AEN_LSC,    12, ncsi_aen_handler_lsc    },
 204        { NCSI_PKT_AEN_CR,      4, ncsi_aen_handler_cr     },
 205        { NCSI_PKT_AEN_HNCDSC,  8, ncsi_aen_handler_hncdsc }
 206};
 207
 208int ncsi_aen_handler(struct ncsi_dev_priv *ndp, struct sk_buff *skb)
 209{
 210        struct ncsi_aen_pkt_hdr *h;
 211        struct ncsi_aen_handler *nah = NULL;
 212        int i, ret;
 213
 214        /* Find the handler */
 215        h = (struct ncsi_aen_pkt_hdr *)skb_network_header(skb);
 216        for (i = 0; i < ARRAY_SIZE(ncsi_aen_handlers); i++) {
 217                if (ncsi_aen_handlers[i].type == h->type) {
 218                        nah = &ncsi_aen_handlers[i];
 219                        break;
 220                }
 221        }
 222
 223        if (!nah) {
 224                netdev_warn(ndp->ndev.dev, "Invalid AEN (0x%x) received\n",
 225                            h->type);
 226                return -ENOENT;
 227        }
 228
 229        ret = ncsi_validate_aen_pkt(h, nah->payload);
 230        if (ret) {
 231                netdev_warn(ndp->ndev.dev,
 232                            "NCSI: 'bad' packet ignored for AEN type 0x%x\n",
 233                            h->type);
 234                goto out;
 235        }
 236
 237        ret = nah->handler(ndp, h);
 238        if (ret)
 239                netdev_err(ndp->ndev.dev,
 240                           "NCSI: Handler for AEN type 0x%x returned %d\n",
 241                           h->type, ret);
 242out:
 243        consume_skb(skb);
 244        return ret;
 245}
 246