linux/net/ncsi/ncsi-aen.c
<<
>>
Prefs
   1/*
   2 * Copyright Gavin Shan, IBM Corporation 2016.
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License as published by
   6 * the Free Software Foundation; either version 2 of the License, or
   7 * (at your option) any later version.
   8 */
   9
  10#include <linux/module.h>
  11#include <linux/kernel.h>
  12#include <linux/init.h>
  13#include <linux/netdevice.h>
  14#include <linux/skbuff.h>
  15
  16#include <net/ncsi.h>
  17#include <net/net_namespace.h>
  18#include <net/sock.h>
  19
  20#include "internal.h"
  21#include "ncsi-pkt.h"
  22
  23static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h,
  24                                 const unsigned short payload)
  25{
  26        u32 checksum;
  27        __be32 *pchecksum;
  28
  29        if (h->common.revision != NCSI_PKT_REVISION)
  30                return -EINVAL;
  31        if (ntohs(h->common.length) != payload)
  32                return -EINVAL;
  33
  34        /* Validate checksum, which might be zeroes if the
  35         * sender doesn't support checksum according to NCSI
  36         * specification.
  37         */
  38        pchecksum = (__be32 *)((void *)(h + 1) + payload - 4);
  39        if (ntohl(*pchecksum) == 0)
  40                return 0;
  41
  42        checksum = ncsi_calculate_checksum((unsigned char *)h,
  43                                           sizeof(*h) + payload - 4);
  44        if (*pchecksum != htonl(checksum))
  45                return -EINVAL;
  46
  47        return 0;
  48}
  49
  50static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp,
  51                                struct ncsi_aen_pkt_hdr *h)
  52{
  53        struct ncsi_aen_lsc_pkt *lsc;
  54        struct ncsi_channel *nc;
  55        struct ncsi_channel_mode *ncm;
  56        bool chained;
  57        int state;
  58        unsigned long old_data, data;
  59        unsigned long flags;
  60
  61        /* Find the NCSI channel */
  62        ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
  63        if (!nc)
  64                return -ENODEV;
  65
  66        /* Update the link status */
  67        lsc = (struct ncsi_aen_lsc_pkt *)h;
  68
  69        spin_lock_irqsave(&nc->lock, flags);
  70        ncm = &nc->modes[NCSI_MODE_LINK];
  71        old_data = ncm->data[2];
  72        data = ntohl(lsc->status);
  73        ncm->data[2] = data;
  74        ncm->data[4] = ntohl(lsc->oem_status);
  75
  76        chained = !list_empty(&nc->link);
  77        state = nc->state;
  78        spin_unlock_irqrestore(&nc->lock, flags);
  79
  80        if (!((old_data ^ data) & 0x1) || chained)
  81                return 0;
  82        if (!(state == NCSI_CHANNEL_INACTIVE && (data & 0x1)) &&
  83            !(state == NCSI_CHANNEL_ACTIVE && !(data & 0x1)))
  84                return 0;
  85
  86        if (!(ndp->flags & NCSI_DEV_HWA) &&
  87            state == NCSI_CHANNEL_ACTIVE)
  88                ndp->flags |= NCSI_DEV_RESHUFFLE;
  89
  90        ncsi_stop_channel_monitor(nc);
  91        spin_lock_irqsave(&ndp->lock, flags);
  92        list_add_tail_rcu(&nc->link, &ndp->channel_queue);
  93        spin_unlock_irqrestore(&ndp->lock, flags);
  94
  95        return ncsi_process_next_channel(ndp);
  96}
  97
  98static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp,
  99                               struct ncsi_aen_pkt_hdr *h)
 100{
 101        struct ncsi_channel *nc;
 102        unsigned long flags;
 103
 104        /* Find the NCSI channel */
 105        ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
 106        if (!nc)
 107                return -ENODEV;
 108
 109        spin_lock_irqsave(&nc->lock, flags);
 110        if (!list_empty(&nc->link) ||
 111            nc->state != NCSI_CHANNEL_ACTIVE) {
 112                spin_unlock_irqrestore(&nc->lock, flags);
 113                return 0;
 114        }
 115        spin_unlock_irqrestore(&nc->lock, flags);
 116
 117        ncsi_stop_channel_monitor(nc);
 118        spin_lock_irqsave(&nc->lock, flags);
 119        nc->state = NCSI_CHANNEL_INVISIBLE;
 120        spin_unlock_irqrestore(&nc->lock, flags);
 121
 122        spin_lock_irqsave(&ndp->lock, flags);
 123        nc->state = NCSI_CHANNEL_INACTIVE;
 124        list_add_tail_rcu(&nc->link, &ndp->channel_queue);
 125        spin_unlock_irqrestore(&ndp->lock, flags);
 126
 127        return ncsi_process_next_channel(ndp);
 128}
 129
 130static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp,
 131                                   struct ncsi_aen_pkt_hdr *h)
 132{
 133        struct ncsi_channel *nc;
 134        struct ncsi_channel_mode *ncm;
 135        struct ncsi_aen_hncdsc_pkt *hncdsc;
 136        unsigned long flags;
 137
 138        /* Find the NCSI channel */
 139        ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
 140        if (!nc)
 141                return -ENODEV;
 142
 143        /* If the channel is active one, we need reconfigure it */
 144        spin_lock_irqsave(&nc->lock, flags);
 145        ncm = &nc->modes[NCSI_MODE_LINK];
 146        hncdsc = (struct ncsi_aen_hncdsc_pkt *)h;
 147        ncm->data[3] = ntohl(hncdsc->status);
 148        if (!list_empty(&nc->link) ||
 149            nc->state != NCSI_CHANNEL_ACTIVE) {
 150                spin_unlock_irqrestore(&nc->lock, flags);
 151                return 0;
 152        }
 153
 154        spin_unlock_irqrestore(&nc->lock, flags);
 155        if (!(ndp->flags & NCSI_DEV_HWA) && !(ncm->data[3] & 0x1))
 156                ndp->flags |= NCSI_DEV_RESHUFFLE;
 157
 158        /* If this channel is the active one and the link doesn't
 159         * work, we have to choose another channel to be active one.
 160         * The logic here is exactly similar to what we do when link
 161         * is down on the active channel.
 162         *
 163         * On the other hand, we need configure it when host driver
 164         * state on the active channel becomes ready.
 165         */
 166        ncsi_stop_channel_monitor(nc);
 167
 168        spin_lock_irqsave(&nc->lock, flags);
 169        nc->state = (ncm->data[3] & 0x1) ? NCSI_CHANNEL_INACTIVE :
 170                                           NCSI_CHANNEL_ACTIVE;
 171        spin_unlock_irqrestore(&nc->lock, flags);
 172
 173        spin_lock_irqsave(&ndp->lock, flags);
 174        list_add_tail_rcu(&nc->link, &ndp->channel_queue);
 175        spin_unlock_irqrestore(&ndp->lock, flags);
 176
 177        ncsi_process_next_channel(ndp);
 178
 179        return 0;
 180}
 181
 182static struct ncsi_aen_handler {
 183        unsigned char type;
 184        int           payload;
 185        int           (*handler)(struct ncsi_dev_priv *ndp,
 186                                 struct ncsi_aen_pkt_hdr *h);
 187} ncsi_aen_handlers[] = {
 188        { NCSI_PKT_AEN_LSC,    12, ncsi_aen_handler_lsc    },
 189        { NCSI_PKT_AEN_CR,      4, ncsi_aen_handler_cr     },
 190        { NCSI_PKT_AEN_HNCDSC,  4, ncsi_aen_handler_hncdsc }
 191};
 192
 193int ncsi_aen_handler(struct ncsi_dev_priv *ndp, struct sk_buff *skb)
 194{
 195        struct ncsi_aen_pkt_hdr *h;
 196        struct ncsi_aen_handler *nah = NULL;
 197        int i, ret;
 198
 199        /* Find the handler */
 200        h = (struct ncsi_aen_pkt_hdr *)skb_network_header(skb);
 201        for (i = 0; i < ARRAY_SIZE(ncsi_aen_handlers); i++) {
 202                if (ncsi_aen_handlers[i].type == h->type) {
 203                        nah = &ncsi_aen_handlers[i];
 204                        break;
 205                }
 206        }
 207
 208        if (!nah) {
 209                netdev_warn(ndp->ndev.dev, "Invalid AEN (0x%x) received\n",
 210                            h->type);
 211                return -ENOENT;
 212        }
 213
 214        ret = ncsi_validate_aen_pkt(h, nah->payload);
 215        if (ret)
 216                goto out;
 217
 218        ret = nah->handler(ndp, h);
 219out:
 220        consume_skb(skb);
 221        return ret;
 222}
 223