Blame view

net/ncsi/ncsi-aen.c 6.11 KB
2874c5fd2   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
2
3
  /*
   * Copyright Gavin Shan, IBM Corporation 2016.
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
   */
  
  #include <linux/module.h>
  #include <linux/kernel.h>
  #include <linux/init.h>
  #include <linux/netdevice.h>
  #include <linux/skbuff.h>
  
  #include <net/ncsi.h>
  #include <net/net_namespace.h>
  #include <net/sock.h>
  
  #include "internal.h"
  #include "ncsi-pkt.h"
  
  static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h,
  				 const unsigned short payload)
  {
  	u32 checksum;
  	__be32 *pchecksum;
  
  	if (h->common.revision != NCSI_PKT_REVISION)
  		return -EINVAL;
  	if (ntohs(h->common.length) != payload)
  		return -EINVAL;
  
  	/* Validate checksum, which might be zeroes if the
  	 * sender doesn't support checksum according to NCSI
  	 * specification.
  	 */
  	pchecksum = (__be32 *)((void *)(h + 1) + payload - 4);
  	if (ntohl(*pchecksum) == 0)
  		return 0;
  
  	checksum = ncsi_calculate_checksum((unsigned char *)h,
  					   sizeof(*h) + payload - 4);
  	if (*pchecksum != htonl(checksum))
  		return -EINVAL;
  
  	return 0;
  }
  
  static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp,
  				struct ncsi_aen_pkt_hdr *h)
  {
8d951a75d   Samuel Mendoza-Jonas   net/ncsi: Configu...
49
  	struct ncsi_channel *nc, *tmp;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
50
  	struct ncsi_channel_mode *ncm;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
51
  	unsigned long old_data, data;
8d951a75d   Samuel Mendoza-Jonas   net/ncsi: Configu...
52
53
  	struct ncsi_aen_lsc_pkt *lsc;
  	struct ncsi_package *np;
0b970e1b0   Samuel Mendoza-Jonas   net/ncsi: Don't m...
54
  	bool had_link, has_link;
8d951a75d   Samuel Mendoza-Jonas   net/ncsi: Configu...
55
56
57
  	unsigned long flags;
  	bool chained;
  	int state;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
58
59
60
61
62
63
64
  
  	/* Find the NCSI channel */
  	ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
  	if (!nc)
  		return -ENODEV;
  
  	/* Update the link status */
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
65
  	lsc = (struct ncsi_aen_lsc_pkt *)h;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
66
67
68
  
  	spin_lock_irqsave(&nc->lock, flags);
  	ncm = &nc->modes[NCSI_MODE_LINK];
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
69
  	old_data = ncm->data[2];
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
70
71
  	data = ntohl(lsc->status);
  	ncm->data[2] = data;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
72
  	ncm->data[4] = ntohl(lsc->oem_status);
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
73

0b970e1b0   Samuel Mendoza-Jonas   net/ncsi: Don't m...
74
75
  	had_link = !!(old_data & 0x1);
  	has_link = !!(data & 0x1);
87975a011   Joel Stanley   net/ncsi: Silence...
76
77
78
  	netdev_dbg(ndp->ndev.dev, "NCSI: LSC AEN - channel %u state %s
  ",
  		   nc->id, data & 0x1 ? "up" : "down");
9ef8690be   Samuel Mendoza-Jonas   net/ncsi: Improve...
79

d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
80
81
82
  	chained = !list_empty(&nc->link);
  	state = nc->state;
  	spin_unlock_irqrestore(&nc->lock, flags);
0b970e1b0   Samuel Mendoza-Jonas   net/ncsi: Don't m...
83
84
85
86
87
88
89
  	if (state == NCSI_CHANNEL_INACTIVE)
  		netdev_warn(ndp->ndev.dev,
  			    "NCSI: Inactive channel %u received AEN!
  ",
  			    nc->id);
  
  	if ((had_link == has_link) || chained)
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
90
  		return 0;
8d951a75d   Samuel Mendoza-Jonas   net/ncsi: Configu...
91
92
93
94
95
96
97
98
99
100
101
102
  	if (!ndp->multi_package && !nc->package->multi_channel) {
  		if (had_link) {
  			ndp->flags |= NCSI_DEV_RESHUFFLE;
  			ncsi_stop_channel_monitor(nc);
  			spin_lock_irqsave(&ndp->lock, flags);
  			list_add_tail_rcu(&nc->link, &ndp->channel_queue);
  			spin_unlock_irqrestore(&ndp->lock, flags);
  			return ncsi_process_next_channel(ndp);
  		}
  		/* Configured channel came up */
  		return 0;
  	}
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
103

8d951a75d   Samuel Mendoza-Jonas   net/ncsi: Configu...
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
  	if (had_link) {
  		ncm = &nc->modes[NCSI_MODE_TX_ENABLE];
  		if (ncsi_channel_is_last(ndp, nc)) {
  			/* No channels left, reconfigure */
  			return ncsi_reset_dev(&ndp->ndev);
  		} else if (ncm->enable) {
  			/* Need to failover Tx channel */
  			ncsi_update_tx_channel(ndp, nc->package, nc, NULL);
  		}
  	} else if (has_link && nc->package->preferred_channel == nc) {
  		/* Return Tx to preferred channel */
  		ncsi_update_tx_channel(ndp, nc->package, NULL, nc);
  	} else if (has_link) {
  		NCSI_FOR_EACH_PACKAGE(ndp, np) {
  			NCSI_FOR_EACH_CHANNEL(np, tmp) {
  				/* Enable Tx on this channel if the current Tx
  				 * channel is down.
  				 */
  				ncm = &tmp->modes[NCSI_MODE_TX_ENABLE];
  				if (ncm->enable &&
  				    !ncsi_channel_has_link(tmp)) {
  					ncsi_update_tx_channel(ndp, nc->package,
  							       tmp, nc);
  					break;
  				}
  			}
  		}
  	}
  
  	/* Leave configured channels active in a multi-channel scenario so
  	 * AEN events are still received.
  	 */
  	return 0;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
137
138
139
140
141
142
143
144
145
146
147
148
  }
  
  static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp,
  			       struct ncsi_aen_pkt_hdr *h)
  {
  	struct ncsi_channel *nc;
  	unsigned long flags;
  
  	/* Find the NCSI channel */
  	ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
  	if (!nc)
  		return -ENODEV;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
149
  	spin_lock_irqsave(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
150
  	if (!list_empty(&nc->link) ||
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
151
152
  	    nc->state != NCSI_CHANNEL_ACTIVE) {
  		spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
153
  		return 0;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
154
155
  	}
  	spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
156
157
  
  	ncsi_stop_channel_monitor(nc);
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
158
159
160
  	spin_lock_irqsave(&nc->lock, flags);
  	nc->state = NCSI_CHANNEL_INVISIBLE;
  	spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
161
  	spin_lock_irqsave(&ndp->lock, flags);
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
162
  	nc->state = NCSI_CHANNEL_INACTIVE;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
  	list_add_tail_rcu(&nc->link, &ndp->channel_queue);
  	spin_unlock_irqrestore(&ndp->lock, flags);
  
  	return ncsi_process_next_channel(ndp);
  }
  
  static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp,
  				   struct ncsi_aen_pkt_hdr *h)
  {
  	struct ncsi_channel *nc;
  	struct ncsi_channel_mode *ncm;
  	struct ncsi_aen_hncdsc_pkt *hncdsc;
  	unsigned long flags;
  
  	/* Find the NCSI channel */
  	ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
  	if (!nc)
  		return -ENODEV;
22d8aa93d   Gavin Shan   net/ncsi: Improve...
181
  	spin_lock_irqsave(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
182
183
184
  	ncm = &nc->modes[NCSI_MODE_LINK];
  	hncdsc = (struct ncsi_aen_hncdsc_pkt *)h;
  	ncm->data[3] = ntohl(hncdsc->status);
22d8aa93d   Gavin Shan   net/ncsi: Improve...
185
  	spin_unlock_irqrestore(&nc->lock, flags);
6e42a3f5c   Joel Stanley   net/ncsi: Use net...
186
187
188
189
  	netdev_dbg(ndp->ndev.dev,
  		   "NCSI: host driver %srunning on channel %u
  ",
  		   ncm->data[3] & 0x1 ? "" : "not ", nc->id);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
190
191
192
193
194
195
196
197
198
199
200
201
  
  	return 0;
  }
  
  static struct ncsi_aen_handler {
  	unsigned char type;
  	int           payload;
  	int           (*handler)(struct ncsi_dev_priv *ndp,
  				 struct ncsi_aen_pkt_hdr *h);
  } ncsi_aen_handlers[] = {
  	{ NCSI_PKT_AEN_LSC,    12, ncsi_aen_handler_lsc    },
  	{ NCSI_PKT_AEN_CR,      4, ncsi_aen_handler_cr     },
6850d0f8b   Samuel Mendoza-Jonas   net/ncsi: Fix AEN...
202
  	{ NCSI_PKT_AEN_HNCDSC,  8, ncsi_aen_handler_hncdsc }
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
  };
  
  int ncsi_aen_handler(struct ncsi_dev_priv *ndp, struct sk_buff *skb)
  {
  	struct ncsi_aen_pkt_hdr *h;
  	struct ncsi_aen_handler *nah = NULL;
  	int i, ret;
  
  	/* Find the handler */
  	h = (struct ncsi_aen_pkt_hdr *)skb_network_header(skb);
  	for (i = 0; i < ARRAY_SIZE(ncsi_aen_handlers); i++) {
  		if (ncsi_aen_handlers[i].type == h->type) {
  			nah = &ncsi_aen_handlers[i];
  			break;
  		}
  	}
  
  	if (!nah) {
  		netdev_warn(ndp->ndev.dev, "Invalid AEN (0x%x) received
  ",
  			    h->type);
  		return -ENOENT;
  	}
  
  	ret = ncsi_validate_aen_pkt(h, nah->payload);
9ef8690be   Samuel Mendoza-Jonas   net/ncsi: Improve...
228
229
230
231
232
  	if (ret) {
  		netdev_warn(ndp->ndev.dev,
  			    "NCSI: 'bad' packet ignored for AEN type 0x%x
  ",
  			    h->type);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
233
  		goto out;
9ef8690be   Samuel Mendoza-Jonas   net/ncsi: Improve...
234
  	}
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
235
236
  
  	ret = nah->handler(ndp, h);
9ef8690be   Samuel Mendoza-Jonas   net/ncsi: Improve...
237
238
239
240
241
  	if (ret)
  		netdev_err(ndp->ndev.dev,
  			   "NCSI: Handler for AEN type 0x%x returned %d
  ",
  			   h->type, ret);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
242
243
244
245
  out:
  	consume_skb(skb);
  	return ret;
  }