337 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			337 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
 | |
| /* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
 | |
| 
 | |
| #include <linux/refcount.h>
 | |
| #include <linux/idr.h>
 | |
| 
 | |
| #include "spectrum.h"
 | |
| #include "reg.h"
 | |
| 
 | |
| struct mlxsw_sp_pgt {
 | |
| 	struct idr pgt_idr;
 | |
| 	u16 end_index; /* Exclusive. */
 | |
| 	struct mutex lock; /* Protects PGT. */
 | |
| 	bool smpe_index_valid;
 | |
| };
 | |
| 
 | |
| struct mlxsw_sp_pgt_entry {
 | |
| 	struct list_head ports_list;
 | |
| 	u16 index;
 | |
| 	u16 smpe_index;
 | |
| };
 | |
| 
 | |
| struct mlxsw_sp_pgt_entry_port {
 | |
| 	struct list_head list; /* Member of 'ports_list'. */
 | |
| 	u16 local_port;
 | |
| };
 | |
| 
 | |
| int mlxsw_sp_pgt_mid_alloc(struct mlxsw_sp *mlxsw_sp, u16 *p_mid)
 | |
| {
 | |
| 	int index, err = 0;
 | |
| 
 | |
| 	mutex_lock(&mlxsw_sp->pgt->lock);
 | |
| 	index = idr_alloc(&mlxsw_sp->pgt->pgt_idr, NULL, 0,
 | |
| 			  mlxsw_sp->pgt->end_index, GFP_KERNEL);
 | |
| 
 | |
| 	if (index < 0) {
 | |
| 		err = index;
 | |
| 		goto err_idr_alloc;
 | |
| 	}
 | |
| 
 | |
| 	*p_mid = index;
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| 	return 0;
 | |
| 
 | |
| err_idr_alloc:
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_pgt_mid_free(struct mlxsw_sp *mlxsw_sp, u16 mid_base)
 | |
| {
 | |
| 	mutex_lock(&mlxsw_sp->pgt->lock);
 | |
| 	WARN_ON(idr_remove(&mlxsw_sp->pgt->pgt_idr, mid_base));
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| }
 | |
| 
 | |
| int mlxsw_sp_pgt_mid_alloc_range(struct mlxsw_sp *mlxsw_sp, u16 *p_mid_base,
 | |
| 				 u16 count)
 | |
| {
 | |
| 	unsigned int mid_base;
 | |
| 	int i, err;
 | |
| 
 | |
| 	mutex_lock(&mlxsw_sp->pgt->lock);
 | |
| 
 | |
| 	mid_base = idr_get_cursor(&mlxsw_sp->pgt->pgt_idr);
 | |
| 	for (i = 0; i < count; i++) {
 | |
| 		err = idr_alloc_cyclic(&mlxsw_sp->pgt->pgt_idr, NULL,
 | |
| 				       mid_base, mid_base + count, GFP_KERNEL);
 | |
| 		if (err < 0)
 | |
| 			goto err_idr_alloc_cyclic;
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| 	*p_mid_base = mid_base;
 | |
| 	return 0;
 | |
| 
 | |
| err_idr_alloc_cyclic:
 | |
| 	for (i--; i >= 0; i--)
 | |
| 		idr_remove(&mlxsw_sp->pgt->pgt_idr, mid_base + i);
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void
 | |
| mlxsw_sp_pgt_mid_free_range(struct mlxsw_sp *mlxsw_sp, u16 mid_base, u16 count)
 | |
| {
 | |
| 	struct idr *pgt_idr = &mlxsw_sp->pgt->pgt_idr;
 | |
| 	int i;
 | |
| 
 | |
| 	mutex_lock(&mlxsw_sp->pgt->lock);
 | |
| 
 | |
| 	for (i = 0; i < count; i++)
 | |
| 		WARN_ON_ONCE(idr_remove(pgt_idr, mid_base + i));
 | |
| 
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_pgt_entry_port *
 | |
| mlxsw_sp_pgt_entry_port_lookup(struct mlxsw_sp_pgt_entry *pgt_entry,
 | |
| 			       u16 local_port)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt_entry_port *pgt_entry_port;
 | |
| 
 | |
| 	list_for_each_entry(pgt_entry_port, &pgt_entry->ports_list, list) {
 | |
| 		if (pgt_entry_port->local_port == local_port)
 | |
| 			return pgt_entry_port;
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_pgt_entry *
 | |
| mlxsw_sp_pgt_entry_create(struct mlxsw_sp_pgt *pgt, u16 mid, u16 smpe)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt_entry *pgt_entry;
 | |
| 	void *ret;
 | |
| 	int err;
 | |
| 
 | |
| 	pgt_entry = kzalloc(sizeof(*pgt_entry), GFP_KERNEL);
 | |
| 	if (!pgt_entry)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	ret = idr_replace(&pgt->pgt_idr, pgt_entry, mid);
 | |
| 	if (IS_ERR(ret)) {
 | |
| 		err = PTR_ERR(ret);
 | |
| 		goto err_idr_replace;
 | |
| 	}
 | |
| 
 | |
| 	INIT_LIST_HEAD(&pgt_entry->ports_list);
 | |
| 	pgt_entry->index = mid;
 | |
| 	pgt_entry->smpe_index = smpe;
 | |
| 	return pgt_entry;
 | |
| 
 | |
| err_idr_replace:
 | |
| 	kfree(pgt_entry);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_pgt_entry_destroy(struct mlxsw_sp_pgt *pgt,
 | |
| 				       struct mlxsw_sp_pgt_entry *pgt_entry)
 | |
| {
 | |
| 	WARN_ON(!list_empty(&pgt_entry->ports_list));
 | |
| 
 | |
| 	pgt_entry = idr_replace(&pgt->pgt_idr, NULL, pgt_entry->index);
 | |
| 	if (WARN_ON(IS_ERR(pgt_entry)))
 | |
| 		return;
 | |
| 
 | |
| 	kfree(pgt_entry);
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_pgt_entry *
 | |
| mlxsw_sp_pgt_entry_get(struct mlxsw_sp_pgt *pgt, u16 mid, u16 smpe)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt_entry *pgt_entry;
 | |
| 
 | |
| 	pgt_entry = idr_find(&pgt->pgt_idr, mid);
 | |
| 	if (pgt_entry)
 | |
| 		return pgt_entry;
 | |
| 
 | |
| 	return mlxsw_sp_pgt_entry_create(pgt, mid, smpe);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_pgt_entry_put(struct mlxsw_sp_pgt *pgt, u16 mid)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt_entry *pgt_entry;
 | |
| 
 | |
| 	pgt_entry = idr_find(&pgt->pgt_idr, mid);
 | |
| 	if (WARN_ON(!pgt_entry))
 | |
| 		return;
 | |
| 
 | |
| 	if (list_empty(&pgt_entry->ports_list))
 | |
| 		mlxsw_sp_pgt_entry_destroy(pgt, pgt_entry);
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_pgt_smid2_port_set(char *smid2_pl, u16 local_port,
 | |
| 					bool member)
 | |
| {
 | |
| 	mlxsw_reg_smid2_port_set(smid2_pl, local_port, member);
 | |
| 	mlxsw_reg_smid2_port_mask_set(smid2_pl, local_port, 1);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mlxsw_sp_pgt_entry_port_write(struct mlxsw_sp *mlxsw_sp,
 | |
| 			      const struct mlxsw_sp_pgt_entry *pgt_entry,
 | |
| 			      u16 local_port, bool member)
 | |
| {
 | |
| 	char *smid2_pl;
 | |
| 	int err;
 | |
| 
 | |
| 	smid2_pl = kmalloc(MLXSW_REG_SMID2_LEN, GFP_KERNEL);
 | |
| 	if (!smid2_pl)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	mlxsw_reg_smid2_pack(smid2_pl, pgt_entry->index, 0, 0,
 | |
| 			     mlxsw_sp->pgt->smpe_index_valid,
 | |
| 			     pgt_entry->smpe_index);
 | |
| 
 | |
| 	mlxsw_sp_pgt_smid2_port_set(smid2_pl, local_port, member);
 | |
| 	err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(smid2), smid2_pl);
 | |
| 
 | |
| 	kfree(smid2_pl);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static struct mlxsw_sp_pgt_entry_port *
 | |
| mlxsw_sp_pgt_entry_port_create(struct mlxsw_sp *mlxsw_sp,
 | |
| 			       struct mlxsw_sp_pgt_entry *pgt_entry,
 | |
| 			       u16 local_port)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt_entry_port *pgt_entry_port;
 | |
| 	int err;
 | |
| 
 | |
| 	pgt_entry_port = kzalloc(sizeof(*pgt_entry_port), GFP_KERNEL);
 | |
| 	if (!pgt_entry_port)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	err = mlxsw_sp_pgt_entry_port_write(mlxsw_sp, pgt_entry, local_port,
 | |
| 					    true);
 | |
| 	if (err)
 | |
| 		goto err_pgt_entry_port_write;
 | |
| 
 | |
| 	pgt_entry_port->local_port = local_port;
 | |
| 	list_add(&pgt_entry_port->list, &pgt_entry->ports_list);
 | |
| 
 | |
| 	return pgt_entry_port;
 | |
| 
 | |
| err_pgt_entry_port_write:
 | |
| 	kfree(pgt_entry_port);
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| 
 | |
| static void
 | |
| mlxsw_sp_pgt_entry_port_destroy(struct mlxsw_sp *mlxsw_sp,
 | |
| 				struct mlxsw_sp_pgt_entry *pgt_entry,
 | |
| 				struct mlxsw_sp_pgt_entry_port *pgt_entry_port)
 | |
| 
 | |
| {
 | |
| 	list_del(&pgt_entry_port->list);
 | |
| 	mlxsw_sp_pgt_entry_port_write(mlxsw_sp, pgt_entry,
 | |
| 				      pgt_entry_port->local_port, false);
 | |
| 	kfree(pgt_entry_port);
 | |
| }
 | |
| 
 | |
| static int mlxsw_sp_pgt_entry_port_add(struct mlxsw_sp *mlxsw_sp, u16 mid,
 | |
| 				       u16 smpe, u16 local_port)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt_entry_port *pgt_entry_port;
 | |
| 	struct mlxsw_sp_pgt_entry *pgt_entry;
 | |
| 	int err;
 | |
| 
 | |
| 	mutex_lock(&mlxsw_sp->pgt->lock);
 | |
| 
 | |
| 	pgt_entry = mlxsw_sp_pgt_entry_get(mlxsw_sp->pgt, mid, smpe);
 | |
| 	if (IS_ERR(pgt_entry)) {
 | |
| 		err = PTR_ERR(pgt_entry);
 | |
| 		goto err_pgt_entry_get;
 | |
| 	}
 | |
| 
 | |
| 	pgt_entry_port = mlxsw_sp_pgt_entry_port_create(mlxsw_sp, pgt_entry,
 | |
| 							local_port);
 | |
| 	if (IS_ERR(pgt_entry_port)) {
 | |
| 		err = PTR_ERR(pgt_entry_port);
 | |
| 		goto err_pgt_entry_port_get;
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| 	return 0;
 | |
| 
 | |
| err_pgt_entry_port_get:
 | |
| 	mlxsw_sp_pgt_entry_put(mlxsw_sp->pgt, mid);
 | |
| err_pgt_entry_get:
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void mlxsw_sp_pgt_entry_port_del(struct mlxsw_sp *mlxsw_sp,
 | |
| 					u16 mid, u16 smpe, u16 local_port)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt_entry_port *pgt_entry_port;
 | |
| 	struct mlxsw_sp_pgt_entry *pgt_entry;
 | |
| 
 | |
| 	mutex_lock(&mlxsw_sp->pgt->lock);
 | |
| 
 | |
| 	pgt_entry = idr_find(&mlxsw_sp->pgt->pgt_idr, mid);
 | |
| 	if (!pgt_entry)
 | |
| 		goto out;
 | |
| 
 | |
| 	pgt_entry_port = mlxsw_sp_pgt_entry_port_lookup(pgt_entry, local_port);
 | |
| 	if (!pgt_entry_port)
 | |
| 		goto out;
 | |
| 
 | |
| 	mlxsw_sp_pgt_entry_port_destroy(mlxsw_sp, pgt_entry, pgt_entry_port);
 | |
| 	mlxsw_sp_pgt_entry_put(mlxsw_sp->pgt, mid);
 | |
| 
 | |
| out:
 | |
| 	mutex_unlock(&mlxsw_sp->pgt->lock);
 | |
| }
 | |
| 
 | |
| int mlxsw_sp_pgt_entry_port_set(struct mlxsw_sp *mlxsw_sp, u16 mid,
 | |
| 				u16 smpe, u16 local_port, bool member)
 | |
| {
 | |
| 	if (member)
 | |
| 		return mlxsw_sp_pgt_entry_port_add(mlxsw_sp, mid, smpe,
 | |
| 						   local_port);
 | |
| 
 | |
| 	mlxsw_sp_pgt_entry_port_del(mlxsw_sp, mid, smpe, local_port);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| int mlxsw_sp_pgt_init(struct mlxsw_sp *mlxsw_sp)
 | |
| {
 | |
| 	struct mlxsw_sp_pgt *pgt;
 | |
| 
 | |
| 	if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, PGT_SIZE))
 | |
| 		return -EIO;
 | |
| 
 | |
| 	pgt = kzalloc(sizeof(*mlxsw_sp->pgt), GFP_KERNEL);
 | |
| 	if (!pgt)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	idr_init(&pgt->pgt_idr);
 | |
| 	pgt->end_index = MLXSW_CORE_RES_GET(mlxsw_sp->core, PGT_SIZE);
 | |
| 	mutex_init(&pgt->lock);
 | |
| 	pgt->smpe_index_valid = mlxsw_sp->pgt_smpe_index_valid;
 | |
| 	mlxsw_sp->pgt = pgt;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void mlxsw_sp_pgt_fini(struct mlxsw_sp *mlxsw_sp)
 | |
| {
 | |
| 	mutex_destroy(&mlxsw_sp->pgt->lock);
 | |
| 	WARN_ON(!idr_is_empty(&mlxsw_sp->pgt->pgt_idr));
 | |
| 	idr_destroy(&mlxsw_sp->pgt->pgt_idr);
 | |
| 	kfree(mlxsw_sp->pgt);
 | |
| }
 |