diff --git a/drivers/net/ieee802154/at86rf230.c b/drivers/net/ieee802154/at86rf230.c index bd1ef0b3bee9..9afb4b9d7c93 100644 --- a/drivers/net/ieee802154/at86rf230.c +++ b/drivers/net/ieee802154/at86rf230.c @@ -753,6 +753,30 @@ at86rf230_set_hw_addr_filt(struct ieee802154_dev *dev, return 0; } +static int +at86rf212_set_txpower(struct ieee802154_dev *dev, int db) +{ + struct at86rf230_local *lp = dev->priv; + int rc; + + /* typical maximum output is 5dBm with RG_PHY_TX_PWR 0x60, lower five + * bits decrease power in 1dB steps. 0x60 represents extra PA gain of + * 0dB. + * thus, supported values for db range from -26 to 5, for 31dB of + * reduction to 0dB of reduction. + */ + if (db > 5 || db < -26) + return -EINVAL; + + db = -(db - 5); + + rc = __at86rf230_write(lp, RG_PHY_TX_PWR, 0x60 | db); + if (rc) + return rc; + + return 0; +} + static struct ieee802154_ops at86rf230_ops = { .owner = THIS_MODULE, .xmit = at86rf230_xmit, @@ -771,6 +795,7 @@ static struct ieee802154_ops at86rf212_ops = { .start = at86rf230_start, .stop = at86rf230_stop, .set_hw_addr_filt = at86rf230_set_hw_addr_filt, + .set_txpower = at86rf212_set_txpower, }; static void at86rf230_irqwork(struct work_struct *work) diff --git a/include/linux/nl802154.h b/include/linux/nl802154.h index fd4f2d1cdf6c..625d19e0a1de 100644 --- a/include/linux/nl802154.h +++ b/include/linux/nl802154.h @@ -70,6 +70,8 @@ enum { IEEE802154_ATTR_PHY_NAME, IEEE802154_ATTR_DEV_TYPE, + IEEE802154_ATTR_TXPOWER, + __IEEE802154_ATTR_MAX, }; @@ -122,6 +124,8 @@ enum { IEEE802154_ADD_IFACE, IEEE802154_DEL_IFACE, + IEEE802154_SET_PHYPARAMS, + __IEEE802154_CMD_MAX, }; diff --git a/include/net/mac802154.h b/include/net/mac802154.h index 807d6b7a943f..8bd2785a663c 100644 --- a/include/net/mac802154.h +++ b/include/net/mac802154.h @@ -113,6 +113,10 @@ struct ieee802154_dev { * Set radio for listening on specific address. * Set the device for listening on specified address. * Returns either zero, or negative errno. + * + * set_txpower: + * Set radio transmit power in dB. Called with pib_lock held. + * Returns either zero, or negative errno. */ struct ieee802154_ops { struct module *owner; @@ -129,6 +133,7 @@ struct ieee802154_ops { unsigned long changed); int (*ieee_addr)(struct ieee802154_dev *dev, u8 addr[IEEE802154_ADDR_LEN]); + int (*set_txpower)(struct ieee802154_dev *dev, int db); }; /* Basic interface to register ieee802154 device */ diff --git a/include/net/wpan-phy.h b/include/net/wpan-phy.h index b52bda8d13b1..47fc0c1bc3c7 100644 --- a/include/net/wpan-phy.h +++ b/include/net/wpan-phy.h @@ -37,14 +37,14 @@ struct wpan_phy { struct mutex pib_lock; /* - * This is a PIB according to 802.15.4-2006. + * This is a PIB according to 802.15.4-2011. * We do not provide timing-related variables, as they * aren't used outside of driver */ u8 current_channel; u8 current_page; u32 channels_supported[32]; - u8 transmit_power; + s8 transmit_power; u8 cca_mode; struct device dev; @@ -54,6 +54,8 @@ struct wpan_phy { const char *name, int type); void (*del_iface)(struct wpan_phy *phy, struct net_device *dev); + int (*set_txpower)(struct wpan_phy *phy, int db); + char priv[0] __attribute__((__aligned__(NETDEV_ALIGN))); }; diff --git a/net/ieee802154/ieee802154.h b/net/ieee802154/ieee802154.h index cee4425b9956..6cbc8965be91 100644 --- a/net/ieee802154/ieee802154.h +++ b/net/ieee802154/ieee802154.h @@ -53,6 +53,7 @@ int ieee802154_list_phy(struct sk_buff *skb, struct genl_info *info); int ieee802154_dump_phy(struct sk_buff *skb, struct netlink_callback *cb); int ieee802154_add_iface(struct sk_buff *skb, struct genl_info *info); int ieee802154_del_iface(struct sk_buff *skb, struct genl_info *info); +int ieee802154_set_phyparams(struct sk_buff *skb, struct genl_info *info); enum ieee802154_mcgrp_ids { IEEE802154_COORD_MCGRP, diff --git a/net/ieee802154/netlink.c b/net/ieee802154/netlink.c index 43f1b2bf469f..67c151bf4b91 100644 --- a/net/ieee802154/netlink.c +++ b/net/ieee802154/netlink.c @@ -115,6 +115,7 @@ static const struct genl_ops ieee8021154_ops[] = { ieee802154_dump_phy), IEEE802154_OP(IEEE802154_ADD_IFACE, ieee802154_add_iface), IEEE802154_OP(IEEE802154_DEL_IFACE, ieee802154_del_iface), + IEEE802154_OP(IEEE802154_SET_PHYPARAMS, ieee802154_set_phyparams), /* see nl-mac.c */ IEEE802154_OP(IEEE802154_ASSOCIATE_REQ, ieee802154_associate_req), IEEE802154_OP(IEEE802154_ASSOCIATE_RESP, ieee802154_associate_resp), diff --git a/net/ieee802154/nl-phy.c b/net/ieee802154/nl-phy.c index 89b265aea151..d3ee62fbae99 100644 --- a/net/ieee802154/nl-phy.c +++ b/net/ieee802154/nl-phy.c @@ -55,7 +55,8 @@ static int ieee802154_nl_fill_phy(struct sk_buff *msg, u32 portid, mutex_lock(&phy->pib_lock); if (nla_put_string(msg, IEEE802154_ATTR_PHY_NAME, wpan_phy_name(phy)) || nla_put_u8(msg, IEEE802154_ATTR_PAGE, phy->current_page) || - nla_put_u8(msg, IEEE802154_ATTR_CHANNEL, phy->current_channel)) + nla_put_u8(msg, IEEE802154_ATTR_CHANNEL, phy->current_channel) || + nla_put_s8(msg, IEEE802154_ATTR_TXPOWER, phy->transmit_power)) goto nla_put_failure; for (i = 0; i < 32; i++) { if (phy->channels_supported[i]) @@ -354,3 +355,49 @@ out_dev: return rc; } + +int ieee802154_set_phyparams(struct sk_buff *skb, struct genl_info *info) +{ + struct wpan_phy *phy; + const char *name; + int txpower; + int rc = -EINVAL; + + pr_debug("%s\n", __func__); + + if (!info->attrs[IEEE802154_ATTR_PHY_NAME]) + return -EINVAL; + + name = nla_data(info->attrs[IEEE802154_ATTR_PHY_NAME]); + if (name[nla_len(info->attrs[IEEE802154_ATTR_PHY_NAME]) - 1] != '\0') + return -EINVAL; /* phy name should be null-terminated */ + + txpower = nla_get_s8(info->attrs[IEEE802154_ATTR_TXPOWER]); + + phy = wpan_phy_find(name); + if (!phy) + return -ENODEV; + + if (!phy->set_txpower) + goto out; + + mutex_lock(&phy->pib_lock); + + rc = phy->set_txpower(phy, txpower); + if (rc < 0) { + mutex_unlock(&phy->pib_lock); + goto out; + } + + phy->transmit_power = txpower; + + mutex_unlock(&phy->pib_lock); + + wpan_phy_put(phy); + + return 0; + +out: + wpan_phy_put(phy); + return rc; +} diff --git a/net/ieee802154/nl_policy.c b/net/ieee802154/nl_policy.c index 6adda4d46f95..90b1d0d2c14e 100644 --- a/net/ieee802154/nl_policy.c +++ b/net/ieee802154/nl_policy.c @@ -52,5 +52,7 @@ const struct nla_policy ieee802154_policy[IEEE802154_ATTR_MAX + 1] = { [IEEE802154_ATTR_DURATION] = { .type = NLA_U8, }, [IEEE802154_ATTR_ED_LIST] = { .len = 27 }, [IEEE802154_ATTR_CHANNEL_PAGE_LIST] = { .len = 32 * 4, }, + + [IEEE802154_ATTR_TXPOWER] = { .type = NLA_S8, }, }; diff --git a/net/ieee802154/wpan-class.c b/net/ieee802154/wpan-class.c index 4dd37615a749..8d6f6704da84 100644 --- a/net/ieee802154/wpan-class.c +++ b/net/ieee802154/wpan-class.c @@ -44,9 +44,7 @@ static DEVICE_ATTR_RO(name); MASTER_SHOW(current_channel, "%d"); MASTER_SHOW(current_page, "%d"); -MASTER_SHOW_COMPLEX(transmit_power, "%d +- %d dB", - ((signed char) (phy->transmit_power << 2)) >> 2, - (phy->transmit_power >> 6) ? (phy->transmit_power >> 6) * 3 : 1); +MASTER_SHOW(transmit_power, "%d +- 1 dB"); MASTER_SHOW(cca_mode, "%d"); static ssize_t channels_supported_show(struct device *dev, diff --git a/net/mac802154/ieee802154_dev.c b/net/mac802154/ieee802154_dev.c index 52ae6646a411..9eb49e0886a4 100644 --- a/net/mac802154/ieee802154_dev.c +++ b/net/mac802154/ieee802154_dev.c @@ -165,6 +165,16 @@ err: return ERR_PTR(err); } +static int mac802154_set_txpower(struct wpan_phy *phy, int db) +{ + struct mac802154_priv *priv = wpan_phy_priv(phy); + + if (!priv->ops->set_txpower) + return -ENOTSUPP; + + return priv->ops->set_txpower(&priv->hw, db); +} + struct ieee802154_dev * ieee802154_alloc_device(size_t priv_data_len, struct ieee802154_ops *ops) { @@ -242,6 +252,7 @@ int ieee802154_register_device(struct ieee802154_dev *dev) priv->phy->add_iface = mac802154_add_iface; priv->phy->del_iface = mac802154_del_iface; + priv->phy->set_txpower = mac802154_set_txpower; rc = wpan_phy_register(priv->phy); if (rc < 0)