Blame view

net/ncsi/ncsi-aen.c 5.72 KB
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
1
2
3
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
49
50
51
52
53
54
55
  /*
   * Copyright Gavin Shan, IBM Corporation 2016.
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   */
  
  #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)
  {
  	struct ncsi_aen_lsc_pkt *lsc;
  	struct ncsi_channel *nc;
  	struct ncsi_channel_mode *ncm;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
56
57
58
  	bool chained;
  	int state;
  	unsigned long old_data, data;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
59
60
61
62
63
64
65
66
  	unsigned long flags;
  
  	/* 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...
67
  	lsc = (struct ncsi_aen_lsc_pkt *)h;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
68
69
70
  
  	spin_lock_irqsave(&nc->lock, flags);
  	ncm = &nc->modes[NCSI_MODE_LINK];
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
71
  	old_data = ncm->data[2];
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
72
73
  	data = ntohl(lsc->status);
  	ncm->data[2] = data;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
74
  	ncm->data[4] = ntohl(lsc->oem_status);
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
75
76
77
78
79
80
  
  	chained = !list_empty(&nc->link);
  	state = nc->state;
  	spin_unlock_irqrestore(&nc->lock, flags);
  
  	if (!((old_data ^ data) & 0x1) || chained)
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
81
  		return 0;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
82
83
  	if (!(state == NCSI_CHANNEL_INACTIVE && (data & 0x1)) &&
  	    !(state == NCSI_CHANNEL_ACTIVE && !(data & 0x1)))
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
84
85
86
  		return 0;
  
  	if (!(ndp->flags & NCSI_DEV_HWA) &&
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
87
  	    state == NCSI_CHANNEL_ACTIVE)
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
  		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);
  }
  
  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...
108
  	spin_lock_irqsave(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
109
  	if (!list_empty(&nc->link) ||
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
110
111
  	    nc->state != NCSI_CHANNEL_ACTIVE) {
  		spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
112
  		return 0;
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
113
114
  	}
  	spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
115
116
  
  	ncsi_stop_channel_monitor(nc);
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
117
118
119
  	spin_lock_irqsave(&nc->lock, flags);
  	nc->state = NCSI_CHANNEL_INVISIBLE;
  	spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
120
  	spin_lock_irqsave(&ndp->lock, flags);
d8cedaabe   Gavin Shan   net/ncsi: Avoid u...
121
  	nc->state = NCSI_CHANNEL_INACTIVE;
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
  	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;
  
  	/* If the channel is active one, we need reconfigure it */
22d8aa93d   Gavin Shan   net/ncsi: Improve...
142
  	spin_lock_irqsave(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
143
144
145
146
  	ncm = &nc->modes[NCSI_MODE_LINK];
  	hncdsc = (struct ncsi_aen_hncdsc_pkt *)h;
  	ncm->data[3] = ntohl(hncdsc->status);
  	if (!list_empty(&nc->link) ||
22d8aa93d   Gavin Shan   net/ncsi: Improve...
147
148
  	    nc->state != NCSI_CHANNEL_ACTIVE) {
  		spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
149
  		return 0;
22d8aa93d   Gavin Shan   net/ncsi: Improve...
150
  	}
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
151

22d8aa93d   Gavin Shan   net/ncsi: Improve...
152
153
  	spin_unlock_irqrestore(&nc->lock, flags);
  	if (!(ndp->flags & NCSI_DEV_HWA) && !(ncm->data[3] & 0x1))
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
154
155
156
157
158
159
  		ndp->flags |= NCSI_DEV_RESHUFFLE;
  
  	/* If this channel is the active one and the link doesn't
  	 * work, we have to choose another channel to be active one.
  	 * The logic here is exactly similar to what we do when link
  	 * is down on the active channel.
22d8aa93d   Gavin Shan   net/ncsi: Improve...
160
161
162
  	 *
  	 * On the other hand, we need configure it when host driver
  	 * state on the active channel becomes ready.
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
163
164
  	 */
  	ncsi_stop_channel_monitor(nc);
22d8aa93d   Gavin Shan   net/ncsi: Improve...
165
166
167
168
169
  
  	spin_lock_irqsave(&nc->lock, flags);
  	nc->state = (ncm->data[3] & 0x1) ? NCSI_CHANNEL_INACTIVE :
  					   NCSI_CHANNEL_ACTIVE;
  	spin_unlock_irqrestore(&nc->lock, flags);
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  	spin_lock_irqsave(&ndp->lock, flags);
  	list_add_tail_rcu(&nc->link, &ndp->channel_queue);
  	spin_unlock_irqrestore(&ndp->lock, flags);
  
  	ncsi_process_next_channel(ndp);
  
  	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...
187
  	{ NCSI_PKT_AEN_HNCDSC,  8, ncsi_aen_handler_hncdsc }
7a82ecf4c   Gavin Shan   net/ncsi: NCSI AE...
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
  };
  
  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);
  	if (ret)
  		goto out;
  
  	ret = nah->handler(ndp, h);
  out:
  	consume_skb(skb);
  	return ret;
  }