diff options
Diffstat (limited to 'drivers/net/wireless/libertas/mesh.c')
| -rw-r--r-- | drivers/net/wireless/libertas/mesh.c | 1140 | 
1 files changed, 526 insertions, 614 deletions
diff --git a/drivers/net/wireless/libertas/mesh.c b/drivers/net/wireless/libertas/mesh.c index acf3bf63ee3..6fef746345b 100644 --- a/drivers/net/wireless/libertas/mesh.c +++ b/drivers/net/wireless/libertas/mesh.c @@ -1,5 +1,8 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/delay.h>  #include <linux/etherdevice.h> +#include <linux/hardirq.h>  #include <linux/netdevice.h>  #include <linux/if_ether.h>  #include <linux/if_arp.h> @@ -12,16 +15,144 @@  #include "cmd.h" +static int lbs_add_mesh(struct lbs_private *priv); + +/*************************************************************************** + * Mesh command handling + */ + +static int lbs_mesh_access(struct lbs_private *priv, uint16_t cmd_action, +		    struct cmd_ds_mesh_access *cmd) +{ +	int ret; + +	lbs_deb_enter_args(LBS_DEB_CMD, "action %d", cmd_action); + +	cmd->hdr.command = cpu_to_le16(CMD_MESH_ACCESS); +	cmd->hdr.size = cpu_to_le16(sizeof(*cmd)); +	cmd->hdr.result = 0; + +	cmd->action = cpu_to_le16(cmd_action); + +	ret = lbs_cmd_with_response(priv, CMD_MESH_ACCESS, cmd); + +	lbs_deb_leave(LBS_DEB_CMD); +	return ret; +} + +static int __lbs_mesh_config_send(struct lbs_private *priv, +				  struct cmd_ds_mesh_config *cmd, +				  uint16_t action, uint16_t type) +{ +	int ret; +	u16 command = CMD_MESH_CONFIG_OLD; + +	lbs_deb_enter(LBS_DEB_CMD); + +	/* +	 * Command id is 0xac for v10 FW along with mesh interface +	 * id in bits 14-13-12. +	 */ +	if (priv->mesh_tlv == TLV_TYPE_MESH_ID) +		command = CMD_MESH_CONFIG | +			  (MESH_IFACE_ID << MESH_IFACE_BIT_OFFSET); + +	cmd->hdr.command = cpu_to_le16(command); +	cmd->hdr.size = cpu_to_le16(sizeof(struct cmd_ds_mesh_config)); +	cmd->hdr.result = 0; + +	cmd->type = cpu_to_le16(type); +	cmd->action = cpu_to_le16(action); + +	ret = lbs_cmd_with_response(priv, command, cmd); + +	lbs_deb_leave(LBS_DEB_CMD); +	return ret; +} + +static int lbs_mesh_config_send(struct lbs_private *priv, +			 struct cmd_ds_mesh_config *cmd, +			 uint16_t action, uint16_t type) +{ +	int ret; + +	if (!(priv->fwcapinfo & FW_CAPINFO_PERSISTENT_CONFIG)) +		return -EOPNOTSUPP; + +	ret = __lbs_mesh_config_send(priv, cmd, action, type); +	return ret; +} + +/* This function is the CMD_MESH_CONFIG legacy function.  It only handles the + * START and STOP actions.  The extended actions supported by CMD_MESH_CONFIG + * are all handled by preparing a struct cmd_ds_mesh_config and passing it to + * lbs_mesh_config_send. + */ +static int lbs_mesh_config(struct lbs_private *priv, uint16_t action, +		uint16_t chan) +{ +	struct cmd_ds_mesh_config cmd; +	struct mrvl_meshie *ie; +	DECLARE_SSID_BUF(ssid); + +	memset(&cmd, 0, sizeof(cmd)); +	cmd.channel = cpu_to_le16(chan); +	ie = (struct mrvl_meshie *)cmd.data; + +	switch (action) { +	case CMD_ACT_MESH_CONFIG_START: +		ie->id = WLAN_EID_VENDOR_SPECIFIC; +		ie->val.oui[0] = 0x00; +		ie->val.oui[1] = 0x50; +		ie->val.oui[2] = 0x43; +		ie->val.type = MARVELL_MESH_IE_TYPE; +		ie->val.subtype = MARVELL_MESH_IE_SUBTYPE; +		ie->val.version = MARVELL_MESH_IE_VERSION; +		ie->val.active_protocol_id = MARVELL_MESH_PROTO_ID_HWMP; +		ie->val.active_metric_id = MARVELL_MESH_METRIC_ID; +		ie->val.mesh_capability = MARVELL_MESH_CAPABILITY; +		ie->val.mesh_id_len = priv->mesh_ssid_len; +		memcpy(ie->val.mesh_id, priv->mesh_ssid, priv->mesh_ssid_len); +		ie->len = sizeof(struct mrvl_meshie_val) - +			IEEE80211_MAX_SSID_LEN + priv->mesh_ssid_len; +		cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie_val)); +		break; +	case CMD_ACT_MESH_CONFIG_STOP: +		break; +	default: +		return -1; +	} +	lbs_deb_cmd("mesh config action %d type %x channel %d SSID %s\n", +		    action, priv->mesh_tlv, chan, +		    print_ssid(ssid, priv->mesh_ssid, priv->mesh_ssid_len)); + +	return __lbs_mesh_config_send(priv, &cmd, action, priv->mesh_tlv); +} + +int lbs_mesh_set_channel(struct lbs_private *priv, u8 channel) +{ +	priv->mesh_channel = channel; +	return lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, channel); +} + +static uint16_t lbs_mesh_get_channel(struct lbs_private *priv) +{ +	return priv->mesh_channel ?: 1; +} +  /***************************************************************************   * Mesh sysfs support   */ -/** +/*   * Attributes exported through sysfs   */  /** - * @brief Get function for sysfs attribute anycast_mask + * lbs_anycast_get - Get function for sysfs attribute anycast_mask + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t lbs_anycast_get(struct device *dev,  		struct device_attribute *attr, char * buf) @@ -40,7 +171,11 @@ static ssize_t lbs_anycast_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute anycast_mask + * lbs_anycast_set - Set function for sysfs attribute anycast_mask + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t lbs_anycast_set(struct device *dev,  		struct device_attribute *attr, const char * buf, size_t count) @@ -62,7 +197,10 @@ static ssize_t lbs_anycast_set(struct device *dev,  }  /** - * @brief Get function for sysfs attribute prb_rsp_limit + * lbs_prb_rsp_limit_get - Get function for sysfs attribute prb_rsp_limit + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t lbs_prb_rsp_limit_get(struct device *dev,  		struct device_attribute *attr, char *buf) @@ -85,7 +223,11 @@ static ssize_t lbs_prb_rsp_limit_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute prb_rsp_limit + * lbs_prb_rsp_limit_set - Set function for sysfs attribute prb_rsp_limit + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t lbs_prb_rsp_limit_set(struct device *dev,  		struct device_attribute *attr, const char *buf, size_t count) @@ -98,7 +240,7 @@ static ssize_t lbs_prb_rsp_limit_set(struct device *dev,  	memset(&mesh_access, 0, sizeof(mesh_access));  	mesh_access.data[0] = cpu_to_le32(CMD_ACT_SET); -	if (!strict_strtoul(buf, 10, &retry_limit)) +	if (!kstrtoul(buf, 10, &retry_limit))  		return -ENOTSUPP;  	if (retry_limit > 15)  		return -ENOTSUPP; @@ -114,7 +256,10 @@ static ssize_t lbs_prb_rsp_limit_set(struct device *dev,  }  /** - * Get function for sysfs attribute mesh + * lbs_mesh_get - Get function for sysfs attribute mesh + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t lbs_mesh_get(struct device *dev,  		struct device_attribute *attr, char * buf) @@ -124,24 +269,22 @@ static ssize_t lbs_mesh_get(struct device *dev,  }  /** - *  Set function for sysfs attribute mesh + * lbs_mesh_set - Set function for sysfs attribute mesh + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t lbs_mesh_set(struct device *dev,  		struct device_attribute *attr, const char * buf, size_t count)  {  	struct lbs_private *priv = to_net_dev(dev)->ml_priv;  	int enable; -	int ret, action = CMD_ACT_MESH_CONFIG_STOP;  	sscanf(buf, "%x", &enable);  	enable = !!enable;  	if (enable == !!priv->mesh_dev)  		return count; -	if (enable) -		action = CMD_ACT_MESH_CONFIG_START; -	ret = lbs_mesh_config(priv, action, priv->channel); -	if (ret) -		return ret;  	if (enable)  		lbs_add_mesh(priv); @@ -151,19 +294,19 @@ static ssize_t lbs_mesh_set(struct device *dev,  	return count;  } -/** +/*   * lbs_mesh attribute to be exported per ethX interface   * through sysfs (/sys/class/net/ethX/lbs_mesh)   */  static DEVICE_ATTR(lbs_mesh, 0644, lbs_mesh_get, lbs_mesh_set); -/** +/*   * anycast_mask attribute to be exported per mshX interface   * through sysfs (/sys/class/net/mshX/anycast_mask)   */  static DEVICE_ATTR(anycast_mask, 0644, lbs_anycast_get, lbs_anycast_set); -/** +/*   * prb_rsp_limit attribute to be exported per mshX interface   * through sysfs (/sys/class/net/mshX/prb_rsp_limit)   */ @@ -176,580 +319,11 @@ static struct attribute *lbs_mesh_sysfs_entries[] = {  	NULL,  }; -static struct attribute_group lbs_mesh_attr_group = { +static const struct attribute_group lbs_mesh_attr_group = {  	.attrs = lbs_mesh_sysfs_entries,  }; - -/*************************************************************************** - * Initializing and starting, stopping mesh - */ - -/* - * Check mesh FW version and appropriately send the mesh start - * command - */ -int lbs_init_mesh(struct lbs_private *priv) -{ -	struct net_device *dev = priv->dev; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_MESH); - -	priv->mesh_connect_status = LBS_DISCONNECTED; - -	/* Determine mesh_fw_ver from fwrelease and fwcapinfo */ -	/* 5.0.16p0 9.0.0.p0 is known to NOT support any mesh */ -	/* 5.110.22 have mesh command with 0xa3 command id */ -	/* 10.0.0.p0 FW brings in mesh config command with different id */ -	/* Check FW version MSB and initialize mesh_fw_ver */ -	if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V5) { -		/* Enable mesh, if supported, and work out which TLV it uses. -		   0x100 + 291 is an unofficial value used in 5.110.20.pXX -		   0x100 + 37 is the official value used in 5.110.21.pXX -		   but we check them in that order because 20.pXX doesn't -		   give an error -- it just silently fails. */ - -		/* 5.110.20.pXX firmware will fail the command if the channel -		   doesn't match the existing channel. But only if the TLV -		   is correct. If the channel is wrong, _BOTH_ versions will -		   give an error to 0x100+291, and allow 0x100+37 to succeed. -		   It's just that 5.110.20.pXX will not have done anything -		   useful */ - -		priv->mesh_tlv = TLV_TYPE_OLD_MESH_ID; -		if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, -				    priv->channel)) { -			priv->mesh_tlv = TLV_TYPE_MESH_ID; -			if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, -					    priv->channel)) -				priv->mesh_tlv = 0; -		} -	} else -	if ((MRVL_FW_MAJOR_REV(priv->fwrelease) >= MRVL_FW_V10) && -		(priv->fwcapinfo & MESH_CAPINFO_ENABLE_MASK)) { -		/* 10.0.0.pXX new firmwares should succeed with TLV -		 * 0x100+37; Do not invoke command with old TLV. -		 */ -		priv->mesh_tlv = TLV_TYPE_MESH_ID; -		if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, -				    priv->channel)) -			priv->mesh_tlv = 0; -	} - - -	if (priv->mesh_tlv) { -		sprintf(priv->mesh_ssid, "mesh"); -		priv->mesh_ssid_len = 4; - -		lbs_add_mesh(priv); - -		if (device_create_file(&dev->dev, &dev_attr_lbs_mesh)) -			lbs_pr_err("cannot register lbs_mesh attribute\n"); - -		ret = 1; -	} - -	lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); -	return ret; -} - - -int lbs_deinit_mesh(struct lbs_private *priv) -{ -	struct net_device *dev = priv->dev; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_MESH); - -	if (priv->mesh_tlv) { -		device_remove_file(&dev->dev, &dev_attr_lbs_mesh); -		ret = 1; -	} - -	lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); -	return ret; -} - - -/** - *  @brief This function closes the mshX interface - * - *  @param dev     A pointer to net_device structure - *  @return 	   0 - */ -static int lbs_mesh_stop(struct net_device *dev) -{ -	struct lbs_private *priv = dev->ml_priv; - -	lbs_deb_enter(LBS_DEB_MESH); -	spin_lock_irq(&priv->driver_lock); - -	priv->mesh_open = 0; -	priv->mesh_connect_status = LBS_DISCONNECTED; - -	netif_stop_queue(dev); -	netif_carrier_off(dev); - -	spin_unlock_irq(&priv->driver_lock); - -	schedule_work(&priv->mcast_work); - -	lbs_deb_leave(LBS_DEB_MESH); -	return 0; -} - -/** - *  @brief This function opens the mshX interface - * - *  @param dev     A pointer to net_device structure - *  @return 	   0 or -EBUSY if monitor mode active - */ -static int lbs_mesh_dev_open(struct net_device *dev) -{ -	struct lbs_private *priv = dev->ml_priv; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_NET); - -	spin_lock_irq(&priv->driver_lock); - -	if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { -		ret = -EBUSY; -		goto out; -	} - -	priv->mesh_open = 1; -	priv->mesh_connect_status = LBS_CONNECTED; -	netif_carrier_on(dev); - -	if (!priv->tx_pending_len) -		netif_wake_queue(dev); - out: - -	spin_unlock_irq(&priv->driver_lock); -	lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret); -	return ret; -} - -static const struct net_device_ops mesh_netdev_ops = { -	.ndo_open		= lbs_mesh_dev_open, -	.ndo_stop 		= lbs_mesh_stop, -	.ndo_start_xmit		= lbs_hard_start_xmit, -	.ndo_set_mac_address	= lbs_set_mac_address, -	.ndo_set_multicast_list = lbs_set_multicast_list, -}; - -/** - * @brief This function adds mshX interface - * - *  @param priv    A pointer to the struct lbs_private structure - *  @return 	   0 if successful, -X otherwise - */ -int lbs_add_mesh(struct lbs_private *priv) -{ -	struct net_device *mesh_dev = NULL; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_MESH); - -	/* Allocate a virtual mesh device */ -	mesh_dev = alloc_netdev(0, "msh%d", ether_setup); -	if (!mesh_dev) { -		lbs_deb_mesh("init mshX device failed\n"); -		ret = -ENOMEM; -		goto done; -	} -	mesh_dev->ml_priv = priv; -	priv->mesh_dev = mesh_dev; - -	mesh_dev->netdev_ops = &mesh_netdev_ops; -	mesh_dev->ethtool_ops = &lbs_ethtool_ops; -	memcpy(mesh_dev->dev_addr, priv->dev->dev_addr, ETH_ALEN); - -	SET_NETDEV_DEV(priv->mesh_dev, priv->dev->dev.parent); - -	mesh_dev->flags |= IFF_BROADCAST | IFF_MULTICAST; -	/* Register virtual mesh interface */ -	ret = register_netdev(mesh_dev); -	if (ret) { -		lbs_pr_err("cannot register mshX virtual interface\n"); -		goto err_free; -	} - -	ret = sysfs_create_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); -	if (ret) -		goto err_unregister; - -	lbs_persist_config_init(mesh_dev); - -	/* Everything successful */ -	ret = 0; -	goto done; - -err_unregister: -	unregister_netdev(mesh_dev); - -err_free: -	free_netdev(mesh_dev); - -done: -	lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); -	return ret; -} - -void lbs_remove_mesh(struct lbs_private *priv) -{ -	struct net_device *mesh_dev; - -	mesh_dev = priv->mesh_dev; -	if (!mesh_dev) -		return; - -	lbs_deb_enter(LBS_DEB_MESH); -	netif_stop_queue(mesh_dev); -	netif_carrier_off(mesh_dev); -	sysfs_remove_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); -	lbs_persist_config_remove(mesh_dev); -	unregister_netdev(mesh_dev); -	priv->mesh_dev = NULL; -	free_netdev(mesh_dev); -	lbs_deb_leave(LBS_DEB_MESH); -} - - - -/*************************************************************************** - * Sending and receiving - */ -struct net_device *lbs_mesh_set_dev(struct lbs_private *priv, -	struct net_device *dev, struct rxpd *rxpd) -{ -	if (priv->mesh_dev) { -		if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID) { -			if (rxpd->rx_control & RxPD_MESH_FRAME) -				dev = priv->mesh_dev; -		} else if (priv->mesh_tlv == TLV_TYPE_MESH_ID) { -			if (rxpd->u.bss.bss_num == MESH_IFACE_ID) -				dev = priv->mesh_dev; -		} -	} -	return dev; -} - - -void lbs_mesh_set_txpd(struct lbs_private *priv, -	struct net_device *dev, struct txpd *txpd) -{ -	if (dev == priv->mesh_dev) { -		if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID) -			txpd->tx_control |= cpu_to_le32(TxPD_MESH_FRAME); -		else if (priv->mesh_tlv == TLV_TYPE_MESH_ID) -			txpd->u.bss.bss_num = MESH_IFACE_ID; -	} -} - - -/*************************************************************************** - * Mesh command handling - */ - -/** - *  @brief Add or delete Mesh Blinding Table entries - * - *  @param priv    	A pointer to struct lbs_private structure - *  @param add  	TRUE to add the entry, FALSE to delete it - *  @param addr1        Destination address to blind or unblind - * - *  @return 	   	0 on success, error on failure - */ -int lbs_mesh_bt_add_del(struct lbs_private *priv, bool add, u8 *addr1) -{ -	struct cmd_ds_bt_access cmd; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_CMD); - -	BUG_ON(addr1 == NULL); - -	memset(&cmd, 0, sizeof(cmd)); -	cmd.hdr.size = cpu_to_le16(sizeof(cmd)); -	memcpy(cmd.addr1, addr1, ETH_ALEN); -	if (add) { -		cmd.action = cpu_to_le16(CMD_ACT_BT_ACCESS_ADD); -		lbs_deb_hex(LBS_DEB_MESH, "BT_ADD: blinded MAC addr", -			addr1, ETH_ALEN); -	} else { -		cmd.action = cpu_to_le16(CMD_ACT_BT_ACCESS_DEL); -		lbs_deb_hex(LBS_DEB_MESH, "BT_DEL: blinded MAC addr", -			addr1, ETH_ALEN); -	} - -	ret = lbs_cmd_with_response(priv, CMD_BT_ACCESS, &cmd); - -	lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); -	return ret; -} - -/** - *  @brief Reset/clear the mesh blinding table - * - *  @param priv    	A pointer to struct lbs_private structure - * - *  @return 	   	0 on success, error on failure - */ -int lbs_mesh_bt_reset(struct lbs_private *priv) -{ -	struct cmd_ds_bt_access cmd; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_CMD); - -	memset(&cmd, 0, sizeof(cmd)); -	cmd.hdr.size = cpu_to_le16(sizeof(cmd)); -	cmd.action = cpu_to_le16(CMD_ACT_BT_ACCESS_RESET); - -	ret = lbs_cmd_with_response(priv, CMD_BT_ACCESS, &cmd); - -	lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); -	return ret; -} - -/** - *  @brief Gets the inverted status of the mesh blinding table - * - *  Normally the firmware "blinds" or ignores traffic from mesh nodes in the - *  table, but an inverted table allows *only* traffic from nodes listed in - *  the table. - * - *  @param priv    	A pointer to struct lbs_private structure - *  @param invert  	On success, TRUE if the blinding table is inverted, - *                        FALSE if it is not inverted - * - *  @return 	   	0 on success, error on failure - */ -int lbs_mesh_bt_get_inverted(struct lbs_private *priv, bool *inverted) -{ -	struct cmd_ds_bt_access cmd; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_CMD); - -	BUG_ON(inverted == NULL); - -	memset(&cmd, 0, sizeof(cmd)); -	cmd.hdr.size = cpu_to_le16(sizeof(cmd)); -	cmd.action = cpu_to_le16(CMD_ACT_BT_ACCESS_GET_INVERT); - -	ret = lbs_cmd_with_response(priv, CMD_BT_ACCESS, &cmd); -	if (ret == 0) -		*inverted = !!cmd.id; - -	lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); -	return ret; -} - -/** - *  @brief Sets the inverted status of the mesh blinding table - * - *  Normally the firmware "blinds" or ignores traffic from mesh nodes in the - *  table, but an inverted table allows *only* traffic from nodes listed in - *  the table. - * - *  @param priv    	A pointer to struct lbs_private structure - *  @param invert  	TRUE to invert the blinding table (only traffic from - *                         listed nodes allowed), FALSE to return it - *                         to normal state (listed nodes ignored) - * - *  @return 	   	0 on success, error on failure - */ -int lbs_mesh_bt_set_inverted(struct lbs_private *priv, bool inverted) -{ -	struct cmd_ds_bt_access cmd; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_CMD); - -	memset(&cmd, 0, sizeof(cmd)); -	cmd.hdr.size = cpu_to_le16(sizeof(cmd)); -	cmd.action = cpu_to_le16(CMD_ACT_BT_ACCESS_SET_INVERT); -	cmd.id = cpu_to_le32(!!inverted); - -	ret = lbs_cmd_with_response(priv, CMD_BT_ACCESS, &cmd); - -	lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); -	return ret; -} - -/** - *  @brief List an entry in the mesh blinding table - * - *  @param priv    	A pointer to struct lbs_private structure - *  @param id		The ID of the entry to list - *  @param addr1	MAC address associated with the table entry - * - *  @return 	   	0 on success, error on failure - */ -int lbs_mesh_bt_get_entry(struct lbs_private *priv, u32 id, u8 *addr1) -{ -	struct cmd_ds_bt_access cmd; -	int ret = 0; - -	lbs_deb_enter(LBS_DEB_CMD); - -	BUG_ON(addr1 == NULL); - -	memset(&cmd, 0, sizeof(cmd)); -	cmd.hdr.size = cpu_to_le16(sizeof(cmd)); -	cmd.action = cpu_to_le16(CMD_ACT_BT_ACCESS_SET_INVERT); -	cmd.id = cpu_to_le32(id); - -	ret = lbs_cmd_with_response(priv, CMD_BT_ACCESS, &cmd); -	if (ret == 0) -		memcpy(addr1, cmd.addr1, sizeof(cmd.addr1)); - -	lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); -	return ret; -} - -/** - *  @brief Access the mesh forwarding table - * - *  @param priv    	A pointer to struct lbs_private structure - *  @param cmd_action	The forwarding table action to perform - *  @param cmd		The pre-filled FWT_ACCESS command - * - *  @return 	   	0 on success and 'cmd' will be filled with the - *                        firmware's response - */ -int lbs_cmd_fwt_access(struct lbs_private *priv, u16 cmd_action, -			struct cmd_ds_fwt_access *cmd) -{ -	int ret; - -	lbs_deb_enter_args(LBS_DEB_CMD, "action %d", cmd_action); - -	cmd->hdr.command = cpu_to_le16(CMD_FWT_ACCESS); -	cmd->hdr.size = cpu_to_le16(sizeof(struct cmd_ds_fwt_access)); -	cmd->hdr.result = 0; -	cmd->action = cpu_to_le16(cmd_action); - -	ret = lbs_cmd_with_response(priv, CMD_FWT_ACCESS, cmd); - -	lbs_deb_leave_args(LBS_DEB_CMD, "ret %d", ret); -	return 0; -} - -int lbs_mesh_access(struct lbs_private *priv, uint16_t cmd_action, -		    struct cmd_ds_mesh_access *cmd) -{ -	int ret; - -	lbs_deb_enter_args(LBS_DEB_CMD, "action %d", cmd_action); - -	cmd->hdr.command = cpu_to_le16(CMD_MESH_ACCESS); -	cmd->hdr.size = cpu_to_le16(sizeof(*cmd)); -	cmd->hdr.result = 0; - -	cmd->action = cpu_to_le16(cmd_action); - -	ret = lbs_cmd_with_response(priv, CMD_MESH_ACCESS, cmd); - -	lbs_deb_leave(LBS_DEB_CMD); -	return ret; -} - -static int __lbs_mesh_config_send(struct lbs_private *priv, -				  struct cmd_ds_mesh_config *cmd, -				  uint16_t action, uint16_t type) -{ -	int ret; -	u16 command = CMD_MESH_CONFIG_OLD; - -	lbs_deb_enter(LBS_DEB_CMD); - -	/* -	 * Command id is 0xac for v10 FW along with mesh interface -	 * id in bits 14-13-12. -	 */ -	if (priv->mesh_tlv == TLV_TYPE_MESH_ID) -		command = CMD_MESH_CONFIG | -			  (MESH_IFACE_ID << MESH_IFACE_BIT_OFFSET); - -	cmd->hdr.command = cpu_to_le16(command); -	cmd->hdr.size = cpu_to_le16(sizeof(struct cmd_ds_mesh_config)); -	cmd->hdr.result = 0; - -	cmd->type = cpu_to_le16(type); -	cmd->action = cpu_to_le16(action); - -	ret = lbs_cmd_with_response(priv, command, cmd); - -	lbs_deb_leave(LBS_DEB_CMD); -	return ret; -} - -int lbs_mesh_config_send(struct lbs_private *priv, -			 struct cmd_ds_mesh_config *cmd, -			 uint16_t action, uint16_t type) -{ -	int ret; - -	if (!(priv->fwcapinfo & FW_CAPINFO_PERSISTENT_CONFIG)) -		return -EOPNOTSUPP; - -	ret = __lbs_mesh_config_send(priv, cmd, action, type); -	return ret; -} - -/* This function is the CMD_MESH_CONFIG legacy function.  It only handles the - * START and STOP actions.  The extended actions supported by CMD_MESH_CONFIG - * are all handled by preparing a struct cmd_ds_mesh_config and passing it to - * lbs_mesh_config_send. - */ -int lbs_mesh_config(struct lbs_private *priv, uint16_t action, uint16_t chan) -{ -	struct cmd_ds_mesh_config cmd; -	struct mrvl_meshie *ie; -	DECLARE_SSID_BUF(ssid); - -	memset(&cmd, 0, sizeof(cmd)); -	cmd.channel = cpu_to_le16(chan); -	ie = (struct mrvl_meshie *)cmd.data; - -	switch (action) { -	case CMD_ACT_MESH_CONFIG_START: -		ie->id = WLAN_EID_GENERIC; -		ie->val.oui[0] = 0x00; -		ie->val.oui[1] = 0x50; -		ie->val.oui[2] = 0x43; -		ie->val.type = MARVELL_MESH_IE_TYPE; -		ie->val.subtype = MARVELL_MESH_IE_SUBTYPE; -		ie->val.version = MARVELL_MESH_IE_VERSION; -		ie->val.active_protocol_id = MARVELL_MESH_PROTO_ID_HWMP; -		ie->val.active_metric_id = MARVELL_MESH_METRIC_ID; -		ie->val.mesh_capability = MARVELL_MESH_CAPABILITY; -		ie->val.mesh_id_len = priv->mesh_ssid_len; -		memcpy(ie->val.mesh_id, priv->mesh_ssid, priv->mesh_ssid_len); -		ie->len = sizeof(struct mrvl_meshie_val) - -			IEEE80211_MAX_SSID_LEN + priv->mesh_ssid_len; -		cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie_val)); -		break; -	case CMD_ACT_MESH_CONFIG_STOP: -		break; -	default: -		return -1; -	} -	lbs_deb_cmd("mesh config action %d type %x channel %d SSID %s\n", -		    action, priv->mesh_tlv, chan, -		    print_ssid(ssid, priv->mesh_ssid, priv->mesh_ssid_len)); - -	return __lbs_mesh_config_send(priv, &cmd, action, priv->mesh_tlv); -} - - -  /***************************************************************************   * Persistent configuration support   */ @@ -774,7 +348,10 @@ static int mesh_get_default_parameters(struct device *dev,  }  /** - * @brief Get function for sysfs attribute bootflag + * bootflag_get - Get function for sysfs attribute bootflag + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t bootflag_get(struct device *dev,  			    struct device_attribute *attr, char *buf) @@ -791,7 +368,11 @@ static ssize_t bootflag_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute bootflag + * bootflag_set - Set function for sysfs attribute bootflag + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t bootflag_set(struct device *dev, struct device_attribute *attr,  			    const char *buf, size_t count) @@ -817,7 +398,10 @@ static ssize_t bootflag_set(struct device *dev, struct device_attribute *attr,  }  /** - * @brief Get function for sysfs attribute boottime + * boottime_get - Get function for sysfs attribute boottime + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t boottime_get(struct device *dev,  			    struct device_attribute *attr, char *buf) @@ -834,7 +418,11 @@ static ssize_t boottime_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute boottime + * boottime_set - Set function for sysfs attribute boottime + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t boottime_set(struct device *dev,  		struct device_attribute *attr, const char *buf, size_t count) @@ -869,7 +457,10 @@ static ssize_t boottime_set(struct device *dev,  }  /** - * @brief Get function for sysfs attribute channel + * channel_get - Get function for sysfs attribute channel + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t channel_get(struct device *dev,  			   struct device_attribute *attr, char *buf) @@ -886,7 +477,11 @@ static ssize_t channel_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute channel + * channel_set - Set function for sysfs attribute channel + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t channel_set(struct device *dev, struct device_attribute *attr,  			   const char *buf, size_t count) @@ -912,13 +507,15 @@ static ssize_t channel_set(struct device *dev, struct device_attribute *attr,  }  /** - * @brief Get function for sysfs attribute mesh_id + * mesh_id_get - Get function for sysfs attribute mesh_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t mesh_id_get(struct device *dev, struct device_attribute *attr,  			   char *buf)  {  	struct mrvl_mesh_defaults defs; -	int maxlen;  	int ret;  	ret = mesh_get_default_parameters(dev, &defs); @@ -927,21 +524,23 @@ static ssize_t mesh_id_get(struct device *dev, struct device_attribute *attr,  		return ret;  	if (defs.meshie.val.mesh_id_len > IEEE80211_MAX_SSID_LEN) { -		lbs_pr_err("inconsistent mesh ID length"); +		dev_err(dev, "inconsistent mesh ID length\n");  		defs.meshie.val.mesh_id_len = IEEE80211_MAX_SSID_LEN;  	} -	/* SSID not null terminated: reserve room for \0 + \n */ -	maxlen = defs.meshie.val.mesh_id_len + 2; -	maxlen = (PAGE_SIZE > maxlen) ? maxlen : PAGE_SIZE; - -	defs.meshie.val.mesh_id[defs.meshie.val.mesh_id_len] = '\0'; +	memcpy(buf, defs.meshie.val.mesh_id, defs.meshie.val.mesh_id_len); +	buf[defs.meshie.val.mesh_id_len] = '\n'; +	buf[defs.meshie.val.mesh_id_len + 1] = '\0'; -	return snprintf(buf, maxlen, "%s\n", defs.meshie.val.mesh_id); +	return defs.meshie.val.mesh_id_len + 1;  }  /** - * @brief Set function for sysfs attribute mesh_id + * mesh_id_set - Set function for sysfs attribute mesh_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t mesh_id_set(struct device *dev, struct device_attribute *attr,  			   const char *buf, size_t count) @@ -983,7 +582,10 @@ static ssize_t mesh_id_set(struct device *dev, struct device_attribute *attr,  }  /** - * @brief Get function for sysfs attribute protocol_id + * protocol_id_get - Get function for sysfs attribute protocol_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t protocol_id_get(struct device *dev,  			       struct device_attribute *attr, char *buf) @@ -1000,7 +602,11 @@ static ssize_t protocol_id_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute protocol_id + * protocol_id_set - Set function for sysfs attribute protocol_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t protocol_id_set(struct device *dev,  		struct device_attribute *attr, const char *buf, size_t count) @@ -1037,7 +643,10 @@ static ssize_t protocol_id_set(struct device *dev,  }  /** - * @brief Get function for sysfs attribute metric_id + * metric_id_get - Get function for sysfs attribute metric_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t metric_id_get(struct device *dev,  		struct device_attribute *attr, char *buf) @@ -1054,7 +663,11 @@ static ssize_t metric_id_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute metric_id + * metric_id_set - Set function for sysfs attribute metric_id + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t metric_id_set(struct device *dev, struct device_attribute *attr,  			     const char *buf, size_t count) @@ -1091,7 +704,10 @@ static ssize_t metric_id_set(struct device *dev, struct device_attribute *attr,  }  /** - * @brief Get function for sysfs attribute capability + * capability_get - Get function for sysfs attribute capability + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer where data will be returned   */  static ssize_t capability_get(struct device *dev,  		struct device_attribute *attr, char *buf) @@ -1108,7 +724,11 @@ static ssize_t capability_get(struct device *dev,  }  /** - * @brief Set function for sysfs attribute capability + * capability_set - Set function for sysfs attribute capability + * @dev: the &struct device + * @attr: device attributes + * @buf: buffer that contains new attribute value + * @count: size of buffer   */  static ssize_t capability_set(struct device *dev, struct device_attribute *attr,  			      const char *buf, size_t count) @@ -1160,7 +780,7 @@ static struct attribute *boot_opts_attrs[] = {  	NULL  }; -static struct attribute_group boot_opts_group = { +static const struct attribute_group boot_opts_group = {  	.name = "boot_options",  	.attrs = boot_opts_attrs,  }; @@ -1173,31 +793,323 @@ static struct attribute *mesh_ie_attrs[] = {  	NULL  }; -static struct attribute_group mesh_ie_group = { +static const struct attribute_group mesh_ie_group = {  	.name = "mesh_ie",  	.attrs = mesh_ie_attrs,  }; -void lbs_persist_config_init(struct net_device *dev) +static void lbs_persist_config_init(struct net_device *dev)  {  	int ret;  	ret = sysfs_create_group(&(dev->dev.kobj), &boot_opts_group);  	ret = sysfs_create_group(&(dev->dev.kobj), &mesh_ie_group);  } -void lbs_persist_config_remove(struct net_device *dev) +static void lbs_persist_config_remove(struct net_device *dev)  {  	sysfs_remove_group(&(dev->dev.kobj), &boot_opts_group);  	sysfs_remove_group(&(dev->dev.kobj), &mesh_ie_group);  } +/*************************************************************************** + * Initializing and starting, stopping mesh + */ + +/* + * Check mesh FW version and appropriately send the mesh start + * command + */ +int lbs_init_mesh(struct lbs_private *priv) +{ +	int ret = 0; + +	lbs_deb_enter(LBS_DEB_MESH); + +	/* Determine mesh_fw_ver from fwrelease and fwcapinfo */ +	/* 5.0.16p0 9.0.0.p0 is known to NOT support any mesh */ +	/* 5.110.22 have mesh command with 0xa3 command id */ +	/* 10.0.0.p0 FW brings in mesh config command with different id */ +	/* Check FW version MSB and initialize mesh_fw_ver */ +	if (MRVL_FW_MAJOR_REV(priv->fwrelease) == MRVL_FW_V5) { +		/* Enable mesh, if supported, and work out which TLV it uses. +		   0x100 + 291 is an unofficial value used in 5.110.20.pXX +		   0x100 + 37 is the official value used in 5.110.21.pXX +		   but we check them in that order because 20.pXX doesn't +		   give an error -- it just silently fails. */ + +		/* 5.110.20.pXX firmware will fail the command if the channel +		   doesn't match the existing channel. But only if the TLV +		   is correct. If the channel is wrong, _BOTH_ versions will +		   give an error to 0x100+291, and allow 0x100+37 to succeed. +		   It's just that 5.110.20.pXX will not have done anything +		   useful */ + +		priv->mesh_tlv = TLV_TYPE_OLD_MESH_ID; +		if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1)) { +			priv->mesh_tlv = TLV_TYPE_MESH_ID; +			if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1)) +				priv->mesh_tlv = 0; +		} +	} else +	if ((MRVL_FW_MAJOR_REV(priv->fwrelease) >= MRVL_FW_V10) && +		(priv->fwcapinfo & MESH_CAPINFO_ENABLE_MASK)) { +		/* 10.0.0.pXX new firmwares should succeed with TLV +		 * 0x100+37; Do not invoke command with old TLV. +		 */ +		priv->mesh_tlv = TLV_TYPE_MESH_ID; +		if (lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, 1)) +			priv->mesh_tlv = 0; +	} + +	/* Stop meshing until interface is brought up */ +	lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_STOP, 1); + +	if (priv->mesh_tlv) { +		sprintf(priv->mesh_ssid, "mesh"); +		priv->mesh_ssid_len = 4; +		ret = 1; +	} + +	lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); +	return ret; +} + +void lbs_start_mesh(struct lbs_private *priv) +{ +	lbs_add_mesh(priv); + +	if (device_create_file(&priv->dev->dev, &dev_attr_lbs_mesh)) +		netdev_err(priv->dev, "cannot register lbs_mesh attribute\n"); +} + +int lbs_deinit_mesh(struct lbs_private *priv) +{ +	struct net_device *dev = priv->dev; +	int ret = 0; + +	lbs_deb_enter(LBS_DEB_MESH); + +	if (priv->mesh_tlv) { +		device_remove_file(&dev->dev, &dev_attr_lbs_mesh); +		ret = 1; +	} + +	lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); +	return ret; +} + + +/** + * lbs_mesh_stop - close the mshX interface + * + * @dev:	A pointer to &net_device structure + * returns:	0 + */ +static int lbs_mesh_stop(struct net_device *dev) +{ +	struct lbs_private *priv = dev->ml_priv; + +	lbs_deb_enter(LBS_DEB_MESH); +	lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_STOP, +		lbs_mesh_get_channel(priv)); + +	spin_lock_irq(&priv->driver_lock); + +	netif_stop_queue(dev); +	netif_carrier_off(dev); + +	spin_unlock_irq(&priv->driver_lock); + +	lbs_update_mcast(priv); +	if (!lbs_iface_active(priv)) +		lbs_stop_iface(priv); + +	lbs_deb_leave(LBS_DEB_MESH); +	return 0; +} + +/** + * lbs_mesh_dev_open - open the mshX interface + * + * @dev:	A pointer to &net_device structure + * returns:	0 or -EBUSY if monitor mode active + */ +static int lbs_mesh_dev_open(struct net_device *dev) +{ +	struct lbs_private *priv = dev->ml_priv; +	int ret = 0; + +	lbs_deb_enter(LBS_DEB_NET); +	if (!priv->iface_running) { +		ret = lbs_start_iface(priv); +		if (ret) +			goto out; +	} + +	spin_lock_irq(&priv->driver_lock); + +	if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { +		ret = -EBUSY; +		spin_unlock_irq(&priv->driver_lock); +		goto out; +	} + +	netif_carrier_on(dev); + +	if (!priv->tx_pending_len) +		netif_wake_queue(dev); + +	spin_unlock_irq(&priv->driver_lock); + +	ret = lbs_mesh_config(priv, CMD_ACT_MESH_CONFIG_START, +		lbs_mesh_get_channel(priv)); + +out: +	lbs_deb_leave_args(LBS_DEB_NET, "ret %d", ret); +	return ret; +} + +static const struct net_device_ops mesh_netdev_ops = { +	.ndo_open		= lbs_mesh_dev_open, +	.ndo_stop 		= lbs_mesh_stop, +	.ndo_start_xmit		= lbs_hard_start_xmit, +	.ndo_set_mac_address	= lbs_set_mac_address, +	.ndo_set_rx_mode	= lbs_set_multicast_list, +}; + +/** + * lbs_add_mesh - add mshX interface + * + * @priv:	A pointer to the &struct lbs_private structure + * returns:	0 if successful, -X otherwise + */ +static int lbs_add_mesh(struct lbs_private *priv) +{ +	struct net_device *mesh_dev = NULL; +	struct wireless_dev *mesh_wdev; +	int ret = 0; + +	lbs_deb_enter(LBS_DEB_MESH); + +	/* Allocate a virtual mesh device */ +	mesh_wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); +	if (!mesh_wdev) { +		lbs_deb_mesh("init mshX wireless device failed\n"); +		ret = -ENOMEM; +		goto done; +	} + +	mesh_dev = alloc_netdev(0, "msh%d", ether_setup); +	if (!mesh_dev) { +		lbs_deb_mesh("init mshX device failed\n"); +		ret = -ENOMEM; +		goto err_free_wdev; +	} + +	mesh_wdev->iftype = NL80211_IFTYPE_MESH_POINT; +	mesh_wdev->wiphy = priv->wdev->wiphy; +	mesh_wdev->netdev = mesh_dev; + +	mesh_dev->ml_priv = priv; +	mesh_dev->ieee80211_ptr = mesh_wdev; +	priv->mesh_dev = mesh_dev; + +	mesh_dev->netdev_ops = &mesh_netdev_ops; +	mesh_dev->ethtool_ops = &lbs_ethtool_ops; +	eth_hw_addr_inherit(mesh_dev, priv->dev); + +	SET_NETDEV_DEV(priv->mesh_dev, priv->dev->dev.parent); + +	mesh_dev->flags |= IFF_BROADCAST | IFF_MULTICAST; +	/* Register virtual mesh interface */ +	ret = register_netdev(mesh_dev); +	if (ret) { +		pr_err("cannot register mshX virtual interface\n"); +		goto err_free_netdev; +	} + +	ret = sysfs_create_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); +	if (ret) +		goto err_unregister; + +	lbs_persist_config_init(mesh_dev); + +	/* Everything successful */ +	ret = 0; +	goto done; + +err_unregister: +	unregister_netdev(mesh_dev); + +err_free_netdev: +	free_netdev(mesh_dev); + +err_free_wdev: +	kfree(mesh_wdev); + +done: +	lbs_deb_leave_args(LBS_DEB_MESH, "ret %d", ret); +	return ret; +} + +void lbs_remove_mesh(struct lbs_private *priv) +{ +	struct net_device *mesh_dev; + +	mesh_dev = priv->mesh_dev; +	if (!mesh_dev) +		return; + +	lbs_deb_enter(LBS_DEB_MESH); +	netif_stop_queue(mesh_dev); +	netif_carrier_off(mesh_dev); +	sysfs_remove_group(&(mesh_dev->dev.kobj), &lbs_mesh_attr_group); +	lbs_persist_config_remove(mesh_dev); +	unregister_netdev(mesh_dev); +	priv->mesh_dev = NULL; +	kfree(mesh_dev->ieee80211_ptr); +	free_netdev(mesh_dev); +	lbs_deb_leave(LBS_DEB_MESH); +} + + +/*************************************************************************** + * Sending and receiving + */ +struct net_device *lbs_mesh_set_dev(struct lbs_private *priv, +	struct net_device *dev, struct rxpd *rxpd) +{ +	if (priv->mesh_dev) { +		if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID) { +			if (rxpd->rx_control & RxPD_MESH_FRAME) +				dev = priv->mesh_dev; +		} else if (priv->mesh_tlv == TLV_TYPE_MESH_ID) { +			if (rxpd->u.bss.bss_num == MESH_IFACE_ID) +				dev = priv->mesh_dev; +		} +	} +	return dev; +} + + +void lbs_mesh_set_txpd(struct lbs_private *priv, +	struct net_device *dev, struct txpd *txpd) +{ +	if (dev == priv->mesh_dev) { +		if (priv->mesh_tlv == TLV_TYPE_OLD_MESH_ID) +			txpd->tx_control |= cpu_to_le32(TxPD_MESH_FRAME); +		else if (priv->mesh_tlv == TLV_TYPE_MESH_ID) +			txpd->u.bss.bss_num = MESH_IFACE_ID; +	} +} +  /***************************************************************************   * Ethtool related   */ -static const char *mesh_stat_strings[] = { +static const char * const mesh_stat_strings[] = {  			"drop_duplicate_bcast",  			"drop_ttl_zero",  			"drop_no_fwd_route",  | 
