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 23 static 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 50 static 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 98 static 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 130 static 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 182 static 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 193 int 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); 219 out: 220 consume_skb(skb); 221 return ret; 222 } 223