162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Copyright (c) 2006 Intel Corporation.  All rights reserved.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * This software is available to you under a choice of one of two
562306a36Sopenharmony_ci * licenses.  You may choose to be licensed under the terms of the GNU
662306a36Sopenharmony_ci * General Public License (GPL) Version 2, available from the file
762306a36Sopenharmony_ci * COPYING in the main directory of this source tree, or the
862306a36Sopenharmony_ci * OpenIB.org BSD license below:
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci *     Redistribution and use in source and binary forms, with or
1162306a36Sopenharmony_ci *     without modification, are permitted provided that the following
1262306a36Sopenharmony_ci *     conditions are met:
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci *      - Redistributions of source code must retain the above
1562306a36Sopenharmony_ci *        copyright notice, this list of conditions and the following
1662306a36Sopenharmony_ci *        disclaimer.
1762306a36Sopenharmony_ci *
1862306a36Sopenharmony_ci *      - Redistributions in binary form must reproduce the above
1962306a36Sopenharmony_ci *        copyright notice, this list of conditions and the following
2062306a36Sopenharmony_ci *        disclaimer in the documentation and/or other materials
2162306a36Sopenharmony_ci *        provided with the distribution.
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
2462306a36Sopenharmony_ci * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
2562306a36Sopenharmony_ci * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
2662306a36Sopenharmony_ci * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
2762306a36Sopenharmony_ci * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
2862306a36Sopenharmony_ci * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
2962306a36Sopenharmony_ci * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3062306a36Sopenharmony_ci * SOFTWARE.
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#include <linux/completion.h>
3462306a36Sopenharmony_ci#include <linux/dma-mapping.h>
3562306a36Sopenharmony_ci#include <linux/err.h>
3662306a36Sopenharmony_ci#include <linux/interrupt.h>
3762306a36Sopenharmony_ci#include <linux/export.h>
3862306a36Sopenharmony_ci#include <linux/slab.h>
3962306a36Sopenharmony_ci#include <linux/bitops.h>
4062306a36Sopenharmony_ci#include <linux/random.h>
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#include <rdma/ib_cache.h>
4362306a36Sopenharmony_ci#include "sa.h"
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic int mcast_add_one(struct ib_device *device);
4662306a36Sopenharmony_cistatic void mcast_remove_one(struct ib_device *device, void *client_data);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic struct ib_client mcast_client = {
4962306a36Sopenharmony_ci	.name   = "ib_multicast",
5062306a36Sopenharmony_ci	.add    = mcast_add_one,
5162306a36Sopenharmony_ci	.remove = mcast_remove_one
5262306a36Sopenharmony_ci};
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic struct ib_sa_client	sa_client;
5562306a36Sopenharmony_cistatic struct workqueue_struct	*mcast_wq;
5662306a36Sopenharmony_cistatic union ib_gid mgid0;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistruct mcast_device;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistruct mcast_port {
6162306a36Sopenharmony_ci	struct mcast_device	*dev;
6262306a36Sopenharmony_ci	spinlock_t		lock;
6362306a36Sopenharmony_ci	struct rb_root		table;
6462306a36Sopenharmony_ci	refcount_t		refcount;
6562306a36Sopenharmony_ci	struct completion	comp;
6662306a36Sopenharmony_ci	u32			port_num;
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistruct mcast_device {
7062306a36Sopenharmony_ci	struct ib_device	*device;
7162306a36Sopenharmony_ci	struct ib_event_handler	event_handler;
7262306a36Sopenharmony_ci	int			start_port;
7362306a36Sopenharmony_ci	int			end_port;
7462306a36Sopenharmony_ci	struct mcast_port	port[];
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cienum mcast_state {
7862306a36Sopenharmony_ci	MCAST_JOINING,
7962306a36Sopenharmony_ci	MCAST_MEMBER,
8062306a36Sopenharmony_ci	MCAST_ERROR,
8162306a36Sopenharmony_ci};
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cienum mcast_group_state {
8462306a36Sopenharmony_ci	MCAST_IDLE,
8562306a36Sopenharmony_ci	MCAST_BUSY,
8662306a36Sopenharmony_ci	MCAST_GROUP_ERROR,
8762306a36Sopenharmony_ci	MCAST_PKEY_EVENT
8862306a36Sopenharmony_ci};
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cienum {
9162306a36Sopenharmony_ci	MCAST_INVALID_PKEY_INDEX = 0xFFFF
9262306a36Sopenharmony_ci};
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistruct mcast_member;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistruct mcast_group {
9762306a36Sopenharmony_ci	struct ib_sa_mcmember_rec rec;
9862306a36Sopenharmony_ci	struct rb_node		node;
9962306a36Sopenharmony_ci	struct mcast_port	*port;
10062306a36Sopenharmony_ci	spinlock_t		lock;
10162306a36Sopenharmony_ci	struct work_struct	work;
10262306a36Sopenharmony_ci	struct list_head	pending_list;
10362306a36Sopenharmony_ci	struct list_head	active_list;
10462306a36Sopenharmony_ci	struct mcast_member	*last_join;
10562306a36Sopenharmony_ci	int			members[NUM_JOIN_MEMBERSHIP_TYPES];
10662306a36Sopenharmony_ci	atomic_t		refcount;
10762306a36Sopenharmony_ci	enum mcast_group_state	state;
10862306a36Sopenharmony_ci	struct ib_sa_query	*query;
10962306a36Sopenharmony_ci	u16			pkey_index;
11062306a36Sopenharmony_ci	u8			leave_state;
11162306a36Sopenharmony_ci	int			retries;
11262306a36Sopenharmony_ci};
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistruct mcast_member {
11562306a36Sopenharmony_ci	struct ib_sa_multicast	multicast;
11662306a36Sopenharmony_ci	struct ib_sa_client	*client;
11762306a36Sopenharmony_ci	struct mcast_group	*group;
11862306a36Sopenharmony_ci	struct list_head	list;
11962306a36Sopenharmony_ci	enum mcast_state	state;
12062306a36Sopenharmony_ci	refcount_t		refcount;
12162306a36Sopenharmony_ci	struct completion	comp;
12262306a36Sopenharmony_ci};
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic void join_handler(int status, struct ib_sa_mcmember_rec *rec,
12562306a36Sopenharmony_ci			 void *context);
12662306a36Sopenharmony_cistatic void leave_handler(int status, struct ib_sa_mcmember_rec *rec,
12762306a36Sopenharmony_ci			  void *context);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic struct mcast_group *mcast_find(struct mcast_port *port,
13062306a36Sopenharmony_ci				      union ib_gid *mgid)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct rb_node *node = port->table.rb_node;
13362306a36Sopenharmony_ci	struct mcast_group *group;
13462306a36Sopenharmony_ci	int ret;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	while (node) {
13762306a36Sopenharmony_ci		group = rb_entry(node, struct mcast_group, node);
13862306a36Sopenharmony_ci		ret = memcmp(mgid->raw, group->rec.mgid.raw, sizeof *mgid);
13962306a36Sopenharmony_ci		if (!ret)
14062306a36Sopenharmony_ci			return group;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		if (ret < 0)
14362306a36Sopenharmony_ci			node = node->rb_left;
14462306a36Sopenharmony_ci		else
14562306a36Sopenharmony_ci			node = node->rb_right;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci	return NULL;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic struct mcast_group *mcast_insert(struct mcast_port *port,
15162306a36Sopenharmony_ci					struct mcast_group *group,
15262306a36Sopenharmony_ci					int allow_duplicates)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct rb_node **link = &port->table.rb_node;
15562306a36Sopenharmony_ci	struct rb_node *parent = NULL;
15662306a36Sopenharmony_ci	struct mcast_group *cur_group;
15762306a36Sopenharmony_ci	int ret;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	while (*link) {
16062306a36Sopenharmony_ci		parent = *link;
16162306a36Sopenharmony_ci		cur_group = rb_entry(parent, struct mcast_group, node);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci		ret = memcmp(group->rec.mgid.raw, cur_group->rec.mgid.raw,
16462306a36Sopenharmony_ci			     sizeof group->rec.mgid);
16562306a36Sopenharmony_ci		if (ret < 0)
16662306a36Sopenharmony_ci			link = &(*link)->rb_left;
16762306a36Sopenharmony_ci		else if (ret > 0)
16862306a36Sopenharmony_ci			link = &(*link)->rb_right;
16962306a36Sopenharmony_ci		else if (allow_duplicates)
17062306a36Sopenharmony_ci			link = &(*link)->rb_left;
17162306a36Sopenharmony_ci		else
17262306a36Sopenharmony_ci			return cur_group;
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci	rb_link_node(&group->node, parent, link);
17562306a36Sopenharmony_ci	rb_insert_color(&group->node, &port->table);
17662306a36Sopenharmony_ci	return NULL;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic void deref_port(struct mcast_port *port)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	if (refcount_dec_and_test(&port->refcount))
18262306a36Sopenharmony_ci		complete(&port->comp);
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic void release_group(struct mcast_group *group)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct mcast_port *port = group->port;
18862306a36Sopenharmony_ci	unsigned long flags;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	spin_lock_irqsave(&port->lock, flags);
19162306a36Sopenharmony_ci	if (atomic_dec_and_test(&group->refcount)) {
19262306a36Sopenharmony_ci		rb_erase(&group->node, &port->table);
19362306a36Sopenharmony_ci		spin_unlock_irqrestore(&port->lock, flags);
19462306a36Sopenharmony_ci		kfree(group);
19562306a36Sopenharmony_ci		deref_port(port);
19662306a36Sopenharmony_ci	} else
19762306a36Sopenharmony_ci		spin_unlock_irqrestore(&port->lock, flags);
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic void deref_member(struct mcast_member *member)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	if (refcount_dec_and_test(&member->refcount))
20362306a36Sopenharmony_ci		complete(&member->comp);
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic void queue_join(struct mcast_member *member)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	struct mcast_group *group = member->group;
20962306a36Sopenharmony_ci	unsigned long flags;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	spin_lock_irqsave(&group->lock, flags);
21262306a36Sopenharmony_ci	list_add_tail(&member->list, &group->pending_list);
21362306a36Sopenharmony_ci	if (group->state == MCAST_IDLE) {
21462306a36Sopenharmony_ci		group->state = MCAST_BUSY;
21562306a36Sopenharmony_ci		atomic_inc(&group->refcount);
21662306a36Sopenharmony_ci		queue_work(mcast_wq, &group->work);
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci	spin_unlock_irqrestore(&group->lock, flags);
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci/*
22262306a36Sopenharmony_ci * A multicast group has four types of members: full member, non member,
22362306a36Sopenharmony_ci * sendonly non member and sendonly full member.
22462306a36Sopenharmony_ci * We need to keep track of the number of members of each
22562306a36Sopenharmony_ci * type based on their join state.  Adjust the number of members the belong to
22662306a36Sopenharmony_ci * the specified join states.
22762306a36Sopenharmony_ci */
22862306a36Sopenharmony_cistatic void adjust_membership(struct mcast_group *group, u8 join_state, int inc)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	int i;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	for (i = 0; i < NUM_JOIN_MEMBERSHIP_TYPES; i++, join_state >>= 1)
23362306a36Sopenharmony_ci		if (join_state & 0x1)
23462306a36Sopenharmony_ci			group->members[i] += inc;
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci/*
23862306a36Sopenharmony_ci * If a multicast group has zero members left for a particular join state, but
23962306a36Sopenharmony_ci * the group is still a member with the SA, we need to leave that join state.
24062306a36Sopenharmony_ci * Determine which join states we still belong to, but that do not have any
24162306a36Sopenharmony_ci * active members.
24262306a36Sopenharmony_ci */
24362306a36Sopenharmony_cistatic u8 get_leave_state(struct mcast_group *group)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	u8 leave_state = 0;
24662306a36Sopenharmony_ci	int i;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	for (i = 0; i < NUM_JOIN_MEMBERSHIP_TYPES; i++)
24962306a36Sopenharmony_ci		if (!group->members[i])
25062306a36Sopenharmony_ci			leave_state |= (0x1 << i);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return leave_state & group->rec.join_state;
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_cistatic int check_selector(ib_sa_comp_mask comp_mask,
25662306a36Sopenharmony_ci			  ib_sa_comp_mask selector_mask,
25762306a36Sopenharmony_ci			  ib_sa_comp_mask value_mask,
25862306a36Sopenharmony_ci			  u8 selector, u8 src_value, u8 dst_value)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	int err;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	if (!(comp_mask & selector_mask) || !(comp_mask & value_mask))
26362306a36Sopenharmony_ci		return 0;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	switch (selector) {
26662306a36Sopenharmony_ci	case IB_SA_GT:
26762306a36Sopenharmony_ci		err = (src_value <= dst_value);
26862306a36Sopenharmony_ci		break;
26962306a36Sopenharmony_ci	case IB_SA_LT:
27062306a36Sopenharmony_ci		err = (src_value >= dst_value);
27162306a36Sopenharmony_ci		break;
27262306a36Sopenharmony_ci	case IB_SA_EQ:
27362306a36Sopenharmony_ci		err = (src_value != dst_value);
27462306a36Sopenharmony_ci		break;
27562306a36Sopenharmony_ci	default:
27662306a36Sopenharmony_ci		err = 0;
27762306a36Sopenharmony_ci		break;
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	return err;
28162306a36Sopenharmony_ci}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_cistatic int cmp_rec(struct ib_sa_mcmember_rec *src,
28462306a36Sopenharmony_ci		   struct ib_sa_mcmember_rec *dst, ib_sa_comp_mask comp_mask)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	/* MGID must already match */
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_PORT_GID &&
28962306a36Sopenharmony_ci	    memcmp(&src->port_gid, &dst->port_gid, sizeof src->port_gid))
29062306a36Sopenharmony_ci		return -EINVAL;
29162306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_QKEY && src->qkey != dst->qkey)
29262306a36Sopenharmony_ci		return -EINVAL;
29362306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_MLID && src->mlid != dst->mlid)
29462306a36Sopenharmony_ci		return -EINVAL;
29562306a36Sopenharmony_ci	if (check_selector(comp_mask, IB_SA_MCMEMBER_REC_MTU_SELECTOR,
29662306a36Sopenharmony_ci			   IB_SA_MCMEMBER_REC_MTU, dst->mtu_selector,
29762306a36Sopenharmony_ci			   src->mtu, dst->mtu))
29862306a36Sopenharmony_ci		return -EINVAL;
29962306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_TRAFFIC_CLASS &&
30062306a36Sopenharmony_ci	    src->traffic_class != dst->traffic_class)
30162306a36Sopenharmony_ci		return -EINVAL;
30262306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_PKEY && src->pkey != dst->pkey)
30362306a36Sopenharmony_ci		return -EINVAL;
30462306a36Sopenharmony_ci	if (check_selector(comp_mask, IB_SA_MCMEMBER_REC_RATE_SELECTOR,
30562306a36Sopenharmony_ci			   IB_SA_MCMEMBER_REC_RATE, dst->rate_selector,
30662306a36Sopenharmony_ci			   src->rate, dst->rate))
30762306a36Sopenharmony_ci		return -EINVAL;
30862306a36Sopenharmony_ci	if (check_selector(comp_mask,
30962306a36Sopenharmony_ci			   IB_SA_MCMEMBER_REC_PACKET_LIFE_TIME_SELECTOR,
31062306a36Sopenharmony_ci			   IB_SA_MCMEMBER_REC_PACKET_LIFE_TIME,
31162306a36Sopenharmony_ci			   dst->packet_life_time_selector,
31262306a36Sopenharmony_ci			   src->packet_life_time, dst->packet_life_time))
31362306a36Sopenharmony_ci		return -EINVAL;
31462306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_SL && src->sl != dst->sl)
31562306a36Sopenharmony_ci		return -EINVAL;
31662306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_FLOW_LABEL &&
31762306a36Sopenharmony_ci	    src->flow_label != dst->flow_label)
31862306a36Sopenharmony_ci		return -EINVAL;
31962306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_HOP_LIMIT &&
32062306a36Sopenharmony_ci	    src->hop_limit != dst->hop_limit)
32162306a36Sopenharmony_ci		return -EINVAL;
32262306a36Sopenharmony_ci	if (comp_mask & IB_SA_MCMEMBER_REC_SCOPE && src->scope != dst->scope)
32362306a36Sopenharmony_ci		return -EINVAL;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	/* join_state checked separately, proxy_join ignored */
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	return 0;
32862306a36Sopenharmony_ci}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_cistatic int send_join(struct mcast_group *group, struct mcast_member *member)
33162306a36Sopenharmony_ci{
33262306a36Sopenharmony_ci	struct mcast_port *port = group->port;
33362306a36Sopenharmony_ci	int ret;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	group->last_join = member;
33662306a36Sopenharmony_ci	ret = ib_sa_mcmember_rec_query(&sa_client, port->dev->device,
33762306a36Sopenharmony_ci				       port->port_num, IB_MGMT_METHOD_SET,
33862306a36Sopenharmony_ci				       &member->multicast.rec,
33962306a36Sopenharmony_ci				       member->multicast.comp_mask,
34062306a36Sopenharmony_ci				       3000, GFP_KERNEL, join_handler, group,
34162306a36Sopenharmony_ci				       &group->query);
34262306a36Sopenharmony_ci	return (ret > 0) ? 0 : ret;
34362306a36Sopenharmony_ci}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_cistatic int send_leave(struct mcast_group *group, u8 leave_state)
34662306a36Sopenharmony_ci{
34762306a36Sopenharmony_ci	struct mcast_port *port = group->port;
34862306a36Sopenharmony_ci	struct ib_sa_mcmember_rec rec;
34962306a36Sopenharmony_ci	int ret;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	rec = group->rec;
35262306a36Sopenharmony_ci	rec.join_state = leave_state;
35362306a36Sopenharmony_ci	group->leave_state = leave_state;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	ret = ib_sa_mcmember_rec_query(&sa_client, port->dev->device,
35662306a36Sopenharmony_ci				       port->port_num, IB_SA_METHOD_DELETE, &rec,
35762306a36Sopenharmony_ci				       IB_SA_MCMEMBER_REC_MGID     |
35862306a36Sopenharmony_ci				       IB_SA_MCMEMBER_REC_PORT_GID |
35962306a36Sopenharmony_ci				       IB_SA_MCMEMBER_REC_JOIN_STATE,
36062306a36Sopenharmony_ci				       3000, GFP_KERNEL, leave_handler,
36162306a36Sopenharmony_ci				       group, &group->query);
36262306a36Sopenharmony_ci	return (ret > 0) ? 0 : ret;
36362306a36Sopenharmony_ci}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_cistatic void join_group(struct mcast_group *group, struct mcast_member *member,
36662306a36Sopenharmony_ci		       u8 join_state)
36762306a36Sopenharmony_ci{
36862306a36Sopenharmony_ci	member->state = MCAST_MEMBER;
36962306a36Sopenharmony_ci	adjust_membership(group, join_state, 1);
37062306a36Sopenharmony_ci	group->rec.join_state |= join_state;
37162306a36Sopenharmony_ci	member->multicast.rec = group->rec;
37262306a36Sopenharmony_ci	member->multicast.rec.join_state = join_state;
37362306a36Sopenharmony_ci	list_move(&member->list, &group->active_list);
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cistatic int fail_join(struct mcast_group *group, struct mcast_member *member,
37762306a36Sopenharmony_ci		     int status)
37862306a36Sopenharmony_ci{
37962306a36Sopenharmony_ci	spin_lock_irq(&group->lock);
38062306a36Sopenharmony_ci	list_del_init(&member->list);
38162306a36Sopenharmony_ci	spin_unlock_irq(&group->lock);
38262306a36Sopenharmony_ci	return member->multicast.callback(status, &member->multicast);
38362306a36Sopenharmony_ci}
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_cistatic void process_group_error(struct mcast_group *group)
38662306a36Sopenharmony_ci{
38762306a36Sopenharmony_ci	struct mcast_member *member;
38862306a36Sopenharmony_ci	int ret = 0;
38962306a36Sopenharmony_ci	u16 pkey_index;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	if (group->state == MCAST_PKEY_EVENT)
39262306a36Sopenharmony_ci		ret = ib_find_pkey(group->port->dev->device,
39362306a36Sopenharmony_ci				   group->port->port_num,
39462306a36Sopenharmony_ci				   be16_to_cpu(group->rec.pkey), &pkey_index);
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	spin_lock_irq(&group->lock);
39762306a36Sopenharmony_ci	if (group->state == MCAST_PKEY_EVENT && !ret &&
39862306a36Sopenharmony_ci	    group->pkey_index == pkey_index)
39962306a36Sopenharmony_ci		goto out;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	while (!list_empty(&group->active_list)) {
40262306a36Sopenharmony_ci		member = list_entry(group->active_list.next,
40362306a36Sopenharmony_ci				    struct mcast_member, list);
40462306a36Sopenharmony_ci		refcount_inc(&member->refcount);
40562306a36Sopenharmony_ci		list_del_init(&member->list);
40662306a36Sopenharmony_ci		adjust_membership(group, member->multicast.rec.join_state, -1);
40762306a36Sopenharmony_ci		member->state = MCAST_ERROR;
40862306a36Sopenharmony_ci		spin_unlock_irq(&group->lock);
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci		ret = member->multicast.callback(-ENETRESET,
41162306a36Sopenharmony_ci						 &member->multicast);
41262306a36Sopenharmony_ci		deref_member(member);
41362306a36Sopenharmony_ci		if (ret)
41462306a36Sopenharmony_ci			ib_sa_free_multicast(&member->multicast);
41562306a36Sopenharmony_ci		spin_lock_irq(&group->lock);
41662306a36Sopenharmony_ci	}
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	group->rec.join_state = 0;
41962306a36Sopenharmony_ciout:
42062306a36Sopenharmony_ci	group->state = MCAST_BUSY;
42162306a36Sopenharmony_ci	spin_unlock_irq(&group->lock);
42262306a36Sopenharmony_ci}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic void mcast_work_handler(struct work_struct *work)
42562306a36Sopenharmony_ci{
42662306a36Sopenharmony_ci	struct mcast_group *group;
42762306a36Sopenharmony_ci	struct mcast_member *member;
42862306a36Sopenharmony_ci	struct ib_sa_multicast *multicast;
42962306a36Sopenharmony_ci	int status, ret;
43062306a36Sopenharmony_ci	u8 join_state;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	group = container_of(work, typeof(*group), work);
43362306a36Sopenharmony_ciretest:
43462306a36Sopenharmony_ci	spin_lock_irq(&group->lock);
43562306a36Sopenharmony_ci	while (!list_empty(&group->pending_list) ||
43662306a36Sopenharmony_ci	       (group->state != MCAST_BUSY)) {
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci		if (group->state != MCAST_BUSY) {
43962306a36Sopenharmony_ci			spin_unlock_irq(&group->lock);
44062306a36Sopenharmony_ci			process_group_error(group);
44162306a36Sopenharmony_ci			goto retest;
44262306a36Sopenharmony_ci		}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci		member = list_entry(group->pending_list.next,
44562306a36Sopenharmony_ci				    struct mcast_member, list);
44662306a36Sopenharmony_ci		multicast = &member->multicast;
44762306a36Sopenharmony_ci		join_state = multicast->rec.join_state;
44862306a36Sopenharmony_ci		refcount_inc(&member->refcount);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci		if (join_state == (group->rec.join_state & join_state)) {
45162306a36Sopenharmony_ci			status = cmp_rec(&group->rec, &multicast->rec,
45262306a36Sopenharmony_ci					 multicast->comp_mask);
45362306a36Sopenharmony_ci			if (!status)
45462306a36Sopenharmony_ci				join_group(group, member, join_state);
45562306a36Sopenharmony_ci			else
45662306a36Sopenharmony_ci				list_del_init(&member->list);
45762306a36Sopenharmony_ci			spin_unlock_irq(&group->lock);
45862306a36Sopenharmony_ci			ret = multicast->callback(status, multicast);
45962306a36Sopenharmony_ci		} else {
46062306a36Sopenharmony_ci			spin_unlock_irq(&group->lock);
46162306a36Sopenharmony_ci			status = send_join(group, member);
46262306a36Sopenharmony_ci			if (!status) {
46362306a36Sopenharmony_ci				deref_member(member);
46462306a36Sopenharmony_ci				return;
46562306a36Sopenharmony_ci			}
46662306a36Sopenharmony_ci			ret = fail_join(group, member, status);
46762306a36Sopenharmony_ci		}
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci		deref_member(member);
47062306a36Sopenharmony_ci		if (ret)
47162306a36Sopenharmony_ci			ib_sa_free_multicast(&member->multicast);
47262306a36Sopenharmony_ci		spin_lock_irq(&group->lock);
47362306a36Sopenharmony_ci	}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	join_state = get_leave_state(group);
47662306a36Sopenharmony_ci	if (join_state) {
47762306a36Sopenharmony_ci		group->rec.join_state &= ~join_state;
47862306a36Sopenharmony_ci		spin_unlock_irq(&group->lock);
47962306a36Sopenharmony_ci		if (send_leave(group, join_state))
48062306a36Sopenharmony_ci			goto retest;
48162306a36Sopenharmony_ci	} else {
48262306a36Sopenharmony_ci		group->state = MCAST_IDLE;
48362306a36Sopenharmony_ci		spin_unlock_irq(&group->lock);
48462306a36Sopenharmony_ci		release_group(group);
48562306a36Sopenharmony_ci	}
48662306a36Sopenharmony_ci}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci/*
48962306a36Sopenharmony_ci * Fail a join request if it is still active - at the head of the pending queue.
49062306a36Sopenharmony_ci */
49162306a36Sopenharmony_cistatic void process_join_error(struct mcast_group *group, int status)
49262306a36Sopenharmony_ci{
49362306a36Sopenharmony_ci	struct mcast_member *member;
49462306a36Sopenharmony_ci	int ret;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	spin_lock_irq(&group->lock);
49762306a36Sopenharmony_ci	member = list_entry(group->pending_list.next,
49862306a36Sopenharmony_ci			    struct mcast_member, list);
49962306a36Sopenharmony_ci	if (group->last_join == member) {
50062306a36Sopenharmony_ci		refcount_inc(&member->refcount);
50162306a36Sopenharmony_ci		list_del_init(&member->list);
50262306a36Sopenharmony_ci		spin_unlock_irq(&group->lock);
50362306a36Sopenharmony_ci		ret = member->multicast.callback(status, &member->multicast);
50462306a36Sopenharmony_ci		deref_member(member);
50562306a36Sopenharmony_ci		if (ret)
50662306a36Sopenharmony_ci			ib_sa_free_multicast(&member->multicast);
50762306a36Sopenharmony_ci	} else
50862306a36Sopenharmony_ci		spin_unlock_irq(&group->lock);
50962306a36Sopenharmony_ci}
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_cistatic void join_handler(int status, struct ib_sa_mcmember_rec *rec,
51262306a36Sopenharmony_ci			 void *context)
51362306a36Sopenharmony_ci{
51462306a36Sopenharmony_ci	struct mcast_group *group = context;
51562306a36Sopenharmony_ci	u16 pkey_index = MCAST_INVALID_PKEY_INDEX;
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	if (status)
51862306a36Sopenharmony_ci		process_join_error(group, status);
51962306a36Sopenharmony_ci	else {
52062306a36Sopenharmony_ci		int mgids_changed, is_mgid0;
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci		if (ib_find_pkey(group->port->dev->device,
52362306a36Sopenharmony_ci				 group->port->port_num, be16_to_cpu(rec->pkey),
52462306a36Sopenharmony_ci				 &pkey_index))
52562306a36Sopenharmony_ci			pkey_index = MCAST_INVALID_PKEY_INDEX;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci		spin_lock_irq(&group->port->lock);
52862306a36Sopenharmony_ci		if (group->state == MCAST_BUSY &&
52962306a36Sopenharmony_ci		    group->pkey_index == MCAST_INVALID_PKEY_INDEX)
53062306a36Sopenharmony_ci			group->pkey_index = pkey_index;
53162306a36Sopenharmony_ci		mgids_changed = memcmp(&rec->mgid, &group->rec.mgid,
53262306a36Sopenharmony_ci				       sizeof(group->rec.mgid));
53362306a36Sopenharmony_ci		group->rec = *rec;
53462306a36Sopenharmony_ci		if (mgids_changed) {
53562306a36Sopenharmony_ci			rb_erase(&group->node, &group->port->table);
53662306a36Sopenharmony_ci			is_mgid0 = !memcmp(&mgid0, &group->rec.mgid,
53762306a36Sopenharmony_ci					   sizeof(mgid0));
53862306a36Sopenharmony_ci			mcast_insert(group->port, group, is_mgid0);
53962306a36Sopenharmony_ci		}
54062306a36Sopenharmony_ci		spin_unlock_irq(&group->port->lock);
54162306a36Sopenharmony_ci	}
54262306a36Sopenharmony_ci	mcast_work_handler(&group->work);
54362306a36Sopenharmony_ci}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_cistatic void leave_handler(int status, struct ib_sa_mcmember_rec *rec,
54662306a36Sopenharmony_ci			  void *context)
54762306a36Sopenharmony_ci{
54862306a36Sopenharmony_ci	struct mcast_group *group = context;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	if (status && group->retries > 0 &&
55162306a36Sopenharmony_ci	    !send_leave(group, group->leave_state))
55262306a36Sopenharmony_ci		group->retries--;
55362306a36Sopenharmony_ci	else
55462306a36Sopenharmony_ci		mcast_work_handler(&group->work);
55562306a36Sopenharmony_ci}
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_cistatic struct mcast_group *acquire_group(struct mcast_port *port,
55862306a36Sopenharmony_ci					 union ib_gid *mgid, gfp_t gfp_mask)
55962306a36Sopenharmony_ci{
56062306a36Sopenharmony_ci	struct mcast_group *group, *cur_group;
56162306a36Sopenharmony_ci	unsigned long flags;
56262306a36Sopenharmony_ci	int is_mgid0;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	is_mgid0 = !memcmp(&mgid0, mgid, sizeof mgid0);
56562306a36Sopenharmony_ci	if (!is_mgid0) {
56662306a36Sopenharmony_ci		spin_lock_irqsave(&port->lock, flags);
56762306a36Sopenharmony_ci		group = mcast_find(port, mgid);
56862306a36Sopenharmony_ci		if (group)
56962306a36Sopenharmony_ci			goto found;
57062306a36Sopenharmony_ci		spin_unlock_irqrestore(&port->lock, flags);
57162306a36Sopenharmony_ci	}
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	group = kzalloc(sizeof *group, gfp_mask);
57462306a36Sopenharmony_ci	if (!group)
57562306a36Sopenharmony_ci		return NULL;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	group->retries = 3;
57862306a36Sopenharmony_ci	group->port = port;
57962306a36Sopenharmony_ci	group->rec.mgid = *mgid;
58062306a36Sopenharmony_ci	group->pkey_index = MCAST_INVALID_PKEY_INDEX;
58162306a36Sopenharmony_ci	INIT_LIST_HEAD(&group->pending_list);
58262306a36Sopenharmony_ci	INIT_LIST_HEAD(&group->active_list);
58362306a36Sopenharmony_ci	INIT_WORK(&group->work, mcast_work_handler);
58462306a36Sopenharmony_ci	spin_lock_init(&group->lock);
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	spin_lock_irqsave(&port->lock, flags);
58762306a36Sopenharmony_ci	cur_group = mcast_insert(port, group, is_mgid0);
58862306a36Sopenharmony_ci	if (cur_group) {
58962306a36Sopenharmony_ci		kfree(group);
59062306a36Sopenharmony_ci		group = cur_group;
59162306a36Sopenharmony_ci	} else
59262306a36Sopenharmony_ci		refcount_inc(&port->refcount);
59362306a36Sopenharmony_cifound:
59462306a36Sopenharmony_ci	atomic_inc(&group->refcount);
59562306a36Sopenharmony_ci	spin_unlock_irqrestore(&port->lock, flags);
59662306a36Sopenharmony_ci	return group;
59762306a36Sopenharmony_ci}
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci/*
60062306a36Sopenharmony_ci * We serialize all join requests to a single group to make our lives much
60162306a36Sopenharmony_ci * easier.  Otherwise, two users could try to join the same group
60262306a36Sopenharmony_ci * simultaneously, with different configurations, one could leave while the
60362306a36Sopenharmony_ci * join is in progress, etc., which makes locking around error recovery
60462306a36Sopenharmony_ci * difficult.
60562306a36Sopenharmony_ci */
60662306a36Sopenharmony_cistruct ib_sa_multicast *
60762306a36Sopenharmony_ciib_sa_join_multicast(struct ib_sa_client *client,
60862306a36Sopenharmony_ci		     struct ib_device *device, u32 port_num,
60962306a36Sopenharmony_ci		     struct ib_sa_mcmember_rec *rec,
61062306a36Sopenharmony_ci		     ib_sa_comp_mask comp_mask, gfp_t gfp_mask,
61162306a36Sopenharmony_ci		     int (*callback)(int status,
61262306a36Sopenharmony_ci				     struct ib_sa_multicast *multicast),
61362306a36Sopenharmony_ci		     void *context)
61462306a36Sopenharmony_ci{
61562306a36Sopenharmony_ci	struct mcast_device *dev;
61662306a36Sopenharmony_ci	struct mcast_member *member;
61762306a36Sopenharmony_ci	struct ib_sa_multicast *multicast;
61862306a36Sopenharmony_ci	int ret;
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	dev = ib_get_client_data(device, &mcast_client);
62162306a36Sopenharmony_ci	if (!dev)
62262306a36Sopenharmony_ci		return ERR_PTR(-ENODEV);
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	member = kmalloc(sizeof *member, gfp_mask);
62562306a36Sopenharmony_ci	if (!member)
62662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	ib_sa_client_get(client);
62962306a36Sopenharmony_ci	member->client = client;
63062306a36Sopenharmony_ci	member->multicast.rec = *rec;
63162306a36Sopenharmony_ci	member->multicast.comp_mask = comp_mask;
63262306a36Sopenharmony_ci	member->multicast.callback = callback;
63362306a36Sopenharmony_ci	member->multicast.context = context;
63462306a36Sopenharmony_ci	init_completion(&member->comp);
63562306a36Sopenharmony_ci	refcount_set(&member->refcount, 1);
63662306a36Sopenharmony_ci	member->state = MCAST_JOINING;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	member->group = acquire_group(&dev->port[port_num - dev->start_port],
63962306a36Sopenharmony_ci				      &rec->mgid, gfp_mask);
64062306a36Sopenharmony_ci	if (!member->group) {
64162306a36Sopenharmony_ci		ret = -ENOMEM;
64262306a36Sopenharmony_ci		goto err;
64362306a36Sopenharmony_ci	}
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	/*
64662306a36Sopenharmony_ci	 * The user will get the multicast structure in their callback.  They
64762306a36Sopenharmony_ci	 * could then free the multicast structure before we can return from
64862306a36Sopenharmony_ci	 * this routine.  So we save the pointer to return before queuing
64962306a36Sopenharmony_ci	 * any callback.
65062306a36Sopenharmony_ci	 */
65162306a36Sopenharmony_ci	multicast = &member->multicast;
65262306a36Sopenharmony_ci	queue_join(member);
65362306a36Sopenharmony_ci	return multicast;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_cierr:
65662306a36Sopenharmony_ci	ib_sa_client_put(client);
65762306a36Sopenharmony_ci	kfree(member);
65862306a36Sopenharmony_ci	return ERR_PTR(ret);
65962306a36Sopenharmony_ci}
66062306a36Sopenharmony_ciEXPORT_SYMBOL(ib_sa_join_multicast);
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_civoid ib_sa_free_multicast(struct ib_sa_multicast *multicast)
66362306a36Sopenharmony_ci{
66462306a36Sopenharmony_ci	struct mcast_member *member;
66562306a36Sopenharmony_ci	struct mcast_group *group;
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci	member = container_of(multicast, struct mcast_member, multicast);
66862306a36Sopenharmony_ci	group = member->group;
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_ci	spin_lock_irq(&group->lock);
67162306a36Sopenharmony_ci	if (member->state == MCAST_MEMBER)
67262306a36Sopenharmony_ci		adjust_membership(group, multicast->rec.join_state, -1);
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	list_del_init(&member->list);
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	if (group->state == MCAST_IDLE) {
67762306a36Sopenharmony_ci		group->state = MCAST_BUSY;
67862306a36Sopenharmony_ci		spin_unlock_irq(&group->lock);
67962306a36Sopenharmony_ci		/* Continue to hold reference on group until callback */
68062306a36Sopenharmony_ci		queue_work(mcast_wq, &group->work);
68162306a36Sopenharmony_ci	} else {
68262306a36Sopenharmony_ci		spin_unlock_irq(&group->lock);
68362306a36Sopenharmony_ci		release_group(group);
68462306a36Sopenharmony_ci	}
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	deref_member(member);
68762306a36Sopenharmony_ci	wait_for_completion(&member->comp);
68862306a36Sopenharmony_ci	ib_sa_client_put(member->client);
68962306a36Sopenharmony_ci	kfree(member);
69062306a36Sopenharmony_ci}
69162306a36Sopenharmony_ciEXPORT_SYMBOL(ib_sa_free_multicast);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ciint ib_sa_get_mcmember_rec(struct ib_device *device, u32 port_num,
69462306a36Sopenharmony_ci			   union ib_gid *mgid, struct ib_sa_mcmember_rec *rec)
69562306a36Sopenharmony_ci{
69662306a36Sopenharmony_ci	struct mcast_device *dev;
69762306a36Sopenharmony_ci	struct mcast_port *port;
69862306a36Sopenharmony_ci	struct mcast_group *group;
69962306a36Sopenharmony_ci	unsigned long flags;
70062306a36Sopenharmony_ci	int ret = 0;
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	dev = ib_get_client_data(device, &mcast_client);
70362306a36Sopenharmony_ci	if (!dev)
70462306a36Sopenharmony_ci		return -ENODEV;
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	port = &dev->port[port_num - dev->start_port];
70762306a36Sopenharmony_ci	spin_lock_irqsave(&port->lock, flags);
70862306a36Sopenharmony_ci	group = mcast_find(port, mgid);
70962306a36Sopenharmony_ci	if (group)
71062306a36Sopenharmony_ci		*rec = group->rec;
71162306a36Sopenharmony_ci	else
71262306a36Sopenharmony_ci		ret = -EADDRNOTAVAIL;
71362306a36Sopenharmony_ci	spin_unlock_irqrestore(&port->lock, flags);
71462306a36Sopenharmony_ci
71562306a36Sopenharmony_ci	return ret;
71662306a36Sopenharmony_ci}
71762306a36Sopenharmony_ciEXPORT_SYMBOL(ib_sa_get_mcmember_rec);
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ci/**
72062306a36Sopenharmony_ci * ib_init_ah_from_mcmember - Initialize AH attribute from multicast
72162306a36Sopenharmony_ci * member record and gid of the device.
72262306a36Sopenharmony_ci * @device:	RDMA device
72362306a36Sopenharmony_ci * @port_num:	Port of the rdma device to consider
72462306a36Sopenharmony_ci * @rec:	Multicast member record to use
72562306a36Sopenharmony_ci * @ndev:	Optional netdevice, applicable only for RoCE
72662306a36Sopenharmony_ci * @gid_type:	GID type to consider
72762306a36Sopenharmony_ci * @ah_attr:	AH attribute to fillup on successful completion
72862306a36Sopenharmony_ci *
72962306a36Sopenharmony_ci * ib_init_ah_from_mcmember() initializes AH attribute based on multicast
73062306a36Sopenharmony_ci * member record and other device properties. On success the caller is
73162306a36Sopenharmony_ci * responsible to call rdma_destroy_ah_attr on the ah_attr. Returns 0 on
73262306a36Sopenharmony_ci * success or appropriate error code.
73362306a36Sopenharmony_ci *
73462306a36Sopenharmony_ci */
73562306a36Sopenharmony_ciint ib_init_ah_from_mcmember(struct ib_device *device, u32 port_num,
73662306a36Sopenharmony_ci			     struct ib_sa_mcmember_rec *rec,
73762306a36Sopenharmony_ci			     struct net_device *ndev,
73862306a36Sopenharmony_ci			     enum ib_gid_type gid_type,
73962306a36Sopenharmony_ci			     struct rdma_ah_attr *ah_attr)
74062306a36Sopenharmony_ci{
74162306a36Sopenharmony_ci	const struct ib_gid_attr *sgid_attr;
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	/* GID table is not based on the netdevice for IB link layer,
74462306a36Sopenharmony_ci	 * so ignore ndev during search.
74562306a36Sopenharmony_ci	 */
74662306a36Sopenharmony_ci	if (rdma_protocol_ib(device, port_num))
74762306a36Sopenharmony_ci		ndev = NULL;
74862306a36Sopenharmony_ci	else if (!rdma_protocol_roce(device, port_num))
74962306a36Sopenharmony_ci		return -EINVAL;
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci	sgid_attr = rdma_find_gid_by_port(device, &rec->port_gid,
75262306a36Sopenharmony_ci					  gid_type, port_num, ndev);
75362306a36Sopenharmony_ci	if (IS_ERR(sgid_attr))
75462306a36Sopenharmony_ci		return PTR_ERR(sgid_attr);
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci	memset(ah_attr, 0, sizeof(*ah_attr));
75762306a36Sopenharmony_ci	ah_attr->type = rdma_ah_find_type(device, port_num);
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci	rdma_ah_set_dlid(ah_attr, be16_to_cpu(rec->mlid));
76062306a36Sopenharmony_ci	rdma_ah_set_sl(ah_attr, rec->sl);
76162306a36Sopenharmony_ci	rdma_ah_set_port_num(ah_attr, port_num);
76262306a36Sopenharmony_ci	rdma_ah_set_static_rate(ah_attr, rec->rate);
76362306a36Sopenharmony_ci	rdma_move_grh_sgid_attr(ah_attr, &rec->mgid,
76462306a36Sopenharmony_ci				be32_to_cpu(rec->flow_label),
76562306a36Sopenharmony_ci				rec->hop_limit,	rec->traffic_class,
76662306a36Sopenharmony_ci				sgid_attr);
76762306a36Sopenharmony_ci	return 0;
76862306a36Sopenharmony_ci}
76962306a36Sopenharmony_ciEXPORT_SYMBOL(ib_init_ah_from_mcmember);
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_cistatic void mcast_groups_event(struct mcast_port *port,
77262306a36Sopenharmony_ci			       enum mcast_group_state state)
77362306a36Sopenharmony_ci{
77462306a36Sopenharmony_ci	struct mcast_group *group;
77562306a36Sopenharmony_ci	struct rb_node *node;
77662306a36Sopenharmony_ci	unsigned long flags;
77762306a36Sopenharmony_ci
77862306a36Sopenharmony_ci	spin_lock_irqsave(&port->lock, flags);
77962306a36Sopenharmony_ci	for (node = rb_first(&port->table); node; node = rb_next(node)) {
78062306a36Sopenharmony_ci		group = rb_entry(node, struct mcast_group, node);
78162306a36Sopenharmony_ci		spin_lock(&group->lock);
78262306a36Sopenharmony_ci		if (group->state == MCAST_IDLE) {
78362306a36Sopenharmony_ci			atomic_inc(&group->refcount);
78462306a36Sopenharmony_ci			queue_work(mcast_wq, &group->work);
78562306a36Sopenharmony_ci		}
78662306a36Sopenharmony_ci		if (group->state != MCAST_GROUP_ERROR)
78762306a36Sopenharmony_ci			group->state = state;
78862306a36Sopenharmony_ci		spin_unlock(&group->lock);
78962306a36Sopenharmony_ci	}
79062306a36Sopenharmony_ci	spin_unlock_irqrestore(&port->lock, flags);
79162306a36Sopenharmony_ci}
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_cistatic void mcast_event_handler(struct ib_event_handler *handler,
79462306a36Sopenharmony_ci				struct ib_event *event)
79562306a36Sopenharmony_ci{
79662306a36Sopenharmony_ci	struct mcast_device *dev;
79762306a36Sopenharmony_ci	int index;
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_ci	dev = container_of(handler, struct mcast_device, event_handler);
80062306a36Sopenharmony_ci	if (!rdma_cap_ib_mcast(dev->device, event->element.port_num))
80162306a36Sopenharmony_ci		return;
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci	index = event->element.port_num - dev->start_port;
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci	switch (event->event) {
80662306a36Sopenharmony_ci	case IB_EVENT_PORT_ERR:
80762306a36Sopenharmony_ci	case IB_EVENT_LID_CHANGE:
80862306a36Sopenharmony_ci	case IB_EVENT_CLIENT_REREGISTER:
80962306a36Sopenharmony_ci		mcast_groups_event(&dev->port[index], MCAST_GROUP_ERROR);
81062306a36Sopenharmony_ci		break;
81162306a36Sopenharmony_ci	case IB_EVENT_PKEY_CHANGE:
81262306a36Sopenharmony_ci		mcast_groups_event(&dev->port[index], MCAST_PKEY_EVENT);
81362306a36Sopenharmony_ci		break;
81462306a36Sopenharmony_ci	default:
81562306a36Sopenharmony_ci		break;
81662306a36Sopenharmony_ci	}
81762306a36Sopenharmony_ci}
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_cistatic int mcast_add_one(struct ib_device *device)
82062306a36Sopenharmony_ci{
82162306a36Sopenharmony_ci	struct mcast_device *dev;
82262306a36Sopenharmony_ci	struct mcast_port *port;
82362306a36Sopenharmony_ci	int i;
82462306a36Sopenharmony_ci	int count = 0;
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_ci	dev = kmalloc(struct_size(dev, port, device->phys_port_cnt),
82762306a36Sopenharmony_ci		      GFP_KERNEL);
82862306a36Sopenharmony_ci	if (!dev)
82962306a36Sopenharmony_ci		return -ENOMEM;
83062306a36Sopenharmony_ci
83162306a36Sopenharmony_ci	dev->start_port = rdma_start_port(device);
83262306a36Sopenharmony_ci	dev->end_port = rdma_end_port(device);
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	for (i = 0; i <= dev->end_port - dev->start_port; i++) {
83562306a36Sopenharmony_ci		if (!rdma_cap_ib_mcast(device, dev->start_port + i))
83662306a36Sopenharmony_ci			continue;
83762306a36Sopenharmony_ci		port = &dev->port[i];
83862306a36Sopenharmony_ci		port->dev = dev;
83962306a36Sopenharmony_ci		port->port_num = dev->start_port + i;
84062306a36Sopenharmony_ci		spin_lock_init(&port->lock);
84162306a36Sopenharmony_ci		port->table = RB_ROOT;
84262306a36Sopenharmony_ci		init_completion(&port->comp);
84362306a36Sopenharmony_ci		refcount_set(&port->refcount, 1);
84462306a36Sopenharmony_ci		++count;
84562306a36Sopenharmony_ci	}
84662306a36Sopenharmony_ci
84762306a36Sopenharmony_ci	if (!count) {
84862306a36Sopenharmony_ci		kfree(dev);
84962306a36Sopenharmony_ci		return -EOPNOTSUPP;
85062306a36Sopenharmony_ci	}
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_ci	dev->device = device;
85362306a36Sopenharmony_ci	ib_set_client_data(device, &mcast_client, dev);
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci	INIT_IB_EVENT_HANDLER(&dev->event_handler, device, mcast_event_handler);
85662306a36Sopenharmony_ci	ib_register_event_handler(&dev->event_handler);
85762306a36Sopenharmony_ci	return 0;
85862306a36Sopenharmony_ci}
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_cistatic void mcast_remove_one(struct ib_device *device, void *client_data)
86162306a36Sopenharmony_ci{
86262306a36Sopenharmony_ci	struct mcast_device *dev = client_data;
86362306a36Sopenharmony_ci	struct mcast_port *port;
86462306a36Sopenharmony_ci	int i;
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci	ib_unregister_event_handler(&dev->event_handler);
86762306a36Sopenharmony_ci	flush_workqueue(mcast_wq);
86862306a36Sopenharmony_ci
86962306a36Sopenharmony_ci	for (i = 0; i <= dev->end_port - dev->start_port; i++) {
87062306a36Sopenharmony_ci		if (rdma_cap_ib_mcast(device, dev->start_port + i)) {
87162306a36Sopenharmony_ci			port = &dev->port[i];
87262306a36Sopenharmony_ci			deref_port(port);
87362306a36Sopenharmony_ci			wait_for_completion(&port->comp);
87462306a36Sopenharmony_ci		}
87562306a36Sopenharmony_ci	}
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci	kfree(dev);
87862306a36Sopenharmony_ci}
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ciint mcast_init(void)
88162306a36Sopenharmony_ci{
88262306a36Sopenharmony_ci	int ret;
88362306a36Sopenharmony_ci
88462306a36Sopenharmony_ci	mcast_wq = alloc_ordered_workqueue("ib_mcast", WQ_MEM_RECLAIM);
88562306a36Sopenharmony_ci	if (!mcast_wq)
88662306a36Sopenharmony_ci		return -ENOMEM;
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_ci	ib_sa_register_client(&sa_client);
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci	ret = ib_register_client(&mcast_client);
89162306a36Sopenharmony_ci	if (ret)
89262306a36Sopenharmony_ci		goto err;
89362306a36Sopenharmony_ci	return 0;
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_cierr:
89662306a36Sopenharmony_ci	ib_sa_unregister_client(&sa_client);
89762306a36Sopenharmony_ci	destroy_workqueue(mcast_wq);
89862306a36Sopenharmony_ci	return ret;
89962306a36Sopenharmony_ci}
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_civoid mcast_cleanup(void)
90262306a36Sopenharmony_ci{
90362306a36Sopenharmony_ci	ib_unregister_client(&mcast_client);
90462306a36Sopenharmony_ci	ib_sa_unregister_client(&sa_client);
90562306a36Sopenharmony_ci	destroy_workqueue(mcast_wq);
90662306a36Sopenharmony_ci}
907