* Re: [PATCH net-next v6 03/12] net: airoha: Introduce airoha_gdm_dev struct
From: Lorenzo Bianconi @ 2026-05-12 20:55 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: Christian Marangi, Benjamin Larsson, linux-arm-kernel,
linux-mediatek, netdev, devicetree, Xuegang Lu
In-Reply-To: <20260511-airoha-eth-multi-serdes-v6-3-c899462c4f75@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 31080 bytes --]
On May 11, Lorenzo Bianconi wrote:
> EN7581 and AN7583 SoCs support connecting multiple external SerDes to GDM3
> or GDM4 ports via a hw arbiter that manages the traffic in a TDM manner.
> As a result multiple net_devices can connect to the same GDM{3,4} port
> and there is a theoretical "1:n" relation between GDM port and
> net_devices.
> Introduce airoha_gdm_dev struct to collect net_device related info (e.g.
> net_device and external phy pointer). Please note this is just a
> preliminary patch and we are still supporting a single net_device for
> each GDM port. Subsequent patches will add support for multiple net_devices
> connected to the same GDM port.
>
> Tested-by: Xuegang Lu <xuegang.lu@airoha.com>
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 309 ++++++++++++++++++-------------
> drivers/net/ethernet/airoha/airoha_eth.h | 13 +-
> drivers/net/ethernet/airoha/airoha_ppe.c | 17 +-
> 3 files changed, 203 insertions(+), 136 deletions(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 3fe2561c85f1..18a89de4d58a 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -600,6 +600,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> struct airoha_qdma_desc *desc = &q->desc[q->tail];
> u32 hash, reason, msg1, desc_ctrl;
> struct airoha_gdm_port *port;
> + struct net_device *netdev;
> int data_len, len, p;
> struct page *page;
>
> @@ -628,6 +629,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> goto free_frag;
>
> port = eth->ports[p];
> + netdev = port->dev->dev;
> if (!q->skb) { /* first buffer */
> q->skb = napi_build_skb(e->buf - AIROHA_RX_HEADROOM,
> q->buf_size);
> @@ -637,8 +639,8 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> skb_reserve(q->skb, AIROHA_RX_HEADROOM);
> __skb_put(q->skb, len);
> skb_mark_for_recycle(q->skb);
> - q->skb->dev = port->dev;
> - q->skb->protocol = eth_type_trans(q->skb, port->dev);
> + q->skb->dev = netdev;
> + q->skb->protocol = eth_type_trans(q->skb, netdev);
> q->skb->ip_summed = CHECKSUM_UNNECESSARY;
> skb_record_rx_queue(q->skb, qid);
> } else { /* scattered frame */
> @@ -656,7 +658,7 @@ static int airoha_qdma_rx_process(struct airoha_queue *q, int budget)
> if (FIELD_GET(QDMA_DESC_MORE_MASK, desc_ctrl))
> continue;
>
> - if (netdev_uses_dsa(port->dev)) {
> + if (netdev_uses_dsa(netdev)) {
> /* PPE module requires untagged packets to work
> * properly and it provides DSA port index via the
> * DMA descriptor. Report DSA tag to the DSA stack
> @@ -850,6 +852,7 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> struct airoha_gdm_port *port = eth->ports[i];
> + struct airoha_gdm_dev *dev;
> int j;
>
> if (!port)
> @@ -858,11 +861,12 @@ static void airoha_qdma_wake_netdev_txqs(struct airoha_queue *q)
> if (port->qdma != qdma)
> continue;
>
> - for (j = 0; j < port->dev->num_tx_queues; j++) {
> + dev = port->dev;
> + for (j = 0; j < dev->dev->num_tx_queues; j++) {
> if (airoha_qdma_get_txq(qdma, j) != qid)
> continue;
>
> - netif_wake_subqueue(port->dev, j);
> + netif_wake_subqueue(dev->dev, j);
> }
> }
> q->txq_stopped = false;
> @@ -1702,19 +1706,20 @@ static void airoha_update_hw_stats(struct airoha_gdm_port *port)
> spin_unlock(&port->stats.lock);
> }
>
> -static int airoha_dev_open(struct net_device *dev)
> +static int airoha_dev_open(struct net_device *netdev)
> {
> - int err, len = ETH_HLEN + dev->mtu + ETH_FCS_LEN;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + int err, len = ETH_HLEN + netdev->mtu + ETH_FCS_LEN;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_qdma *qdma = port->qdma;
> u32 pse_port = FE_PSE_PORT_PPE1;
>
> - netif_tx_start_all_queues(dev);
> + netif_tx_start_all_queues(netdev);
> err = airoha_set_vip_for_gdm_port(port, true);
> if (err)
> return err;
>
> - if (netdev_uses_dsa(dev))
> + if (netdev_uses_dsa(netdev))
> airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
> GDM_STAG_EN_MASK);
> else
> @@ -1742,16 +1747,17 @@ static int airoha_dev_open(struct net_device *dev)
> return 0;
> }
>
> -static int airoha_dev_stop(struct net_device *dev)
> +static int airoha_dev_stop(struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_qdma *qdma = port->qdma;
> int i;
>
> - netif_tx_disable(dev);
> + netif_tx_disable(netdev);
> airoha_set_vip_for_gdm_port(port, false);
> - for (i = 0; i < dev->num_tx_queues; i++)
> - netdev_tx_reset_subqueue(dev, i);
> + for (i = 0; i < netdev->num_tx_queues; i++)
> + netdev_tx_reset_subqueue(netdev, i);
>
> airoha_set_gdm_port_fwd_cfg(qdma->eth, REG_GDM_FWD_CFG(port->id),
> FE_PSE_PORT_DROP);
> @@ -1772,16 +1778,17 @@ static int airoha_dev_stop(struct net_device *dev)
> return 0;
> }
>
> -static int airoha_dev_set_macaddr(struct net_device *dev, void *p)
> +static int airoha_dev_set_macaddr(struct net_device *netdev, void *p)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int err;
>
> - err = eth_mac_addr(dev, p);
> + err = eth_mac_addr(netdev, p);
> if (err)
> return err;
>
> - airoha_set_macaddr(port, dev->dev_addr);
> + airoha_set_macaddr(port, netdev->dev_addr);
>
> return 0;
> }
> @@ -1845,16 +1852,17 @@ static int airoha_set_gdm2_loopback(struct airoha_gdm_port *port)
> return 0;
> }
>
> -static int airoha_dev_init(struct net_device *dev)
> +static int airoha_dev_init(struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> - struct airoha_eth *eth = port->eth;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> + struct airoha_eth *eth = dev->eth;
> int i;
>
> /* QDMA0 is used for lan ports while QDMA1 is used for WAN ports */
> port->qdma = ð->qdma[!airoha_is_lan_gdm_port(port)];
> - port->dev->irq = port->qdma->irq_banks[0].irq;
> - airoha_set_macaddr(port, dev->dev_addr);
> + dev->dev->irq = port->qdma->irq_banks[0].irq;
> + airoha_set_macaddr(port, netdev->dev_addr);
>
> switch (port->id) {
> case AIROHA_GDM3_IDX:
> @@ -1879,10 +1887,11 @@ static int airoha_dev_init(struct net_device *dev)
> return 0;
> }
>
> -static void airoha_dev_get_stats64(struct net_device *dev,
> +static void airoha_dev_get_stats64(struct net_device *netdev,
> struct rtnl_link_stats64 *storage)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(port);
> @@ -1901,36 +1910,39 @@ static void airoha_dev_get_stats64(struct net_device *dev,
> } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> }
>
> -static int airoha_dev_change_mtu(struct net_device *dev, int mtu)
> +static int airoha_dev_change_mtu(struct net_device *netdev, int mtu)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = port->qdma->eth;
> u32 len = ETH_HLEN + mtu + ETH_FCS_LEN;
>
> airoha_fe_rmw(eth, REG_GDM_LEN_CFG(port->id),
> GDM_LONG_LEN_MASK,
> FIELD_PREP(GDM_LONG_LEN_MASK, len));
> - WRITE_ONCE(dev->mtu, mtu);
> + WRITE_ONCE(netdev->mtu, mtu);
>
> return 0;
> }
>
> -static u16 airoha_dev_select_queue(struct net_device *dev, struct sk_buff *skb,
> +static u16 airoha_dev_select_queue(struct net_device *netdev,
> + struct sk_buff *skb,
> struct net_device *sb_dev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int queue, channel;
>
> /* For dsa device select QoS channel according to the dsa user port
> * index, rely on port id otherwise. Select QoS queue based on the
> * skb priority.
> */
> - channel = netdev_uses_dsa(dev) ? skb_get_queue_mapping(skb) : port->id;
> + channel = netdev_uses_dsa(netdev) ? skb_get_queue_mapping(skb) : port->id;
> channel = channel % AIROHA_NUM_QOS_CHANNELS;
> queue = (skb->priority - 1) % AIROHA_NUM_QOS_QUEUES; /* QoS queue */
> queue = channel * AIROHA_NUM_QOS_QUEUES + queue;
>
> - return queue < dev->num_tx_queues ? queue : 0;
> + return queue < netdev->num_tx_queues ? queue : 0;
> }
>
> static u32 airoha_get_dsa_tag(struct sk_buff *skb, struct net_device *dev)
> @@ -1994,9 +2006,10 @@ int airoha_get_fe_port(struct airoha_gdm_port *port)
> }
>
> static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> - struct net_device *dev)
> + struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_qdma *qdma = port->qdma;
> u32 nr_frags, tag, msg0, msg1, len;
> struct airoha_queue_entry *e;
> @@ -2009,7 +2022,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> u8 fport;
>
> qid = airoha_qdma_get_txq(qdma, skb_get_queue_mapping(skb));
> - tag = airoha_get_dsa_tag(skb, dev);
> + tag = airoha_get_dsa_tag(skb, netdev);
>
> msg0 = FIELD_PREP(QDMA_ETH_TXMSG_CHAN_MASK,
> qid / AIROHA_NUM_QOS_QUEUES) |
> @@ -2045,7 +2058,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
>
> spin_lock_bh(&q->lock);
>
> - txq = skb_get_tx_queue(dev, skb);
> + txq = skb_get_tx_queue(netdev, skb);
> nr_frags = 1 + skb_shinfo(skb)->nr_frags;
>
> if (q->queued + nr_frags >= q->ndesc) {
> @@ -2069,9 +2082,9 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> dma_addr_t addr;
> u32 val;
>
> - addr = dma_map_single(dev->dev.parent, data, len,
> + addr = dma_map_single(netdev->dev.parent, data, len,
> DMA_TO_DEVICE);
> - if (unlikely(dma_mapping_error(dev->dev.parent, addr)))
> + if (unlikely(dma_mapping_error(netdev->dev.parent, addr)))
> goto error_unmap;
>
> list_move_tail(&e->list, &tx_list);
> @@ -2120,7 +2133,7 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
>
> error_unmap:
> list_for_each_entry(e, &tx_list, list) {
> - dma_unmap_single(dev->dev.parent, e->dma_addr, e->dma_len,
> + dma_unmap_single(netdev->dev.parent, e->dma_addr, e->dma_len,
> DMA_TO_DEVICE);
> e->dma_addr = 0;
> }
> @@ -2129,25 +2142,27 @@ static netdev_tx_t airoha_dev_xmit(struct sk_buff *skb,
> spin_unlock_bh(&q->lock);
> error:
> dev_kfree_skb_any(skb);
> - dev->stats.tx_dropped++;
> + netdev->stats.tx_dropped++;
>
> return NETDEV_TX_OK;
> }
>
> -static void airoha_ethtool_get_drvinfo(struct net_device *dev,
> +static void airoha_ethtool_get_drvinfo(struct net_device *netdev,
> struct ethtool_drvinfo *info)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = port->qdma->eth;
>
> strscpy(info->driver, eth->dev->driver->name, sizeof(info->driver));
> strscpy(info->bus_info, dev_name(eth->dev), sizeof(info->bus_info));
> }
>
> -static void airoha_ethtool_get_mac_stats(struct net_device *dev,
> +static void airoha_ethtool_get_mac_stats(struct net_device *netdev,
> struct ethtool_eth_mac_stats *stats)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> unsigned int start;
>
> airoha_update_hw_stats(port);
> @@ -2175,11 +2190,12 @@ static const struct ethtool_rmon_hist_range airoha_ethtool_rmon_ranges[] = {
> };
>
> static void
> -airoha_ethtool_get_rmon_stats(struct net_device *dev,
> +airoha_ethtool_get_rmon_stats(struct net_device *netdev,
> struct ethtool_rmon_stats *stats,
> const struct ethtool_rmon_hist_range **ranges)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_hw_stats *hw_stats = &port->stats;
> unsigned int start;
>
> @@ -2204,11 +2220,12 @@ airoha_ethtool_get_rmon_stats(struct net_device *dev,
> } while (u64_stats_fetch_retry(&port->stats.syncp, start));
> }
>
> -static int airoha_qdma_set_chan_tx_sched(struct net_device *dev,
> +static int airoha_qdma_set_chan_tx_sched(struct net_device *netdev,
> int channel, enum tx_sched_mode mode,
> const u16 *weights, u8 n_weights)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int i;
>
> for (i = 0; i < AIROHA_NUM_TX_RING; i++)
> @@ -2293,10 +2310,12 @@ static int airoha_qdma_set_tx_ets_sched(struct net_device *dev, int channel,
> ARRAY_SIZE(w));
> }
>
> -static int airoha_qdma_get_tx_ets_stats(struct net_device *dev, int channel,
> +static int airoha_qdma_get_tx_ets_stats(struct net_device *netdev, int channel,
> struct tc_ets_qopt_offload *opt)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> +
> u64 cpu_tx_packets = airoha_qdma_rr(port->qdma,
> REG_CNTR_VAL(channel << 1));
> u64 fwd_tx_packets = airoha_qdma_rr(port->qdma,
> @@ -2558,11 +2577,12 @@ static int airoha_qdma_set_trtcm_token_bucket(struct airoha_qdma *qdma,
> mode, val);
> }
>
> -static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
> +static int airoha_qdma_set_tx_rate_limit(struct net_device *netdev,
> int channel, u32 rate,
> u32 bucket_size)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int i, err;
>
> for (i = 0; i <= TRTCM_PEAK_MODE; i++) {
> @@ -2582,20 +2602,22 @@ static int airoha_qdma_set_tx_rate_limit(struct net_device *dev,
> return 0;
> }
>
> -static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
> +static int airoha_tc_htb_alloc_leaf_queue(struct net_device *netdev,
> struct tc_htb_qopt_offload *opt)
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> u32 rate = div_u64(opt->rate, 1000) << 3; /* kbps */
> - int err, num_tx_queues = dev->real_num_tx_queues;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + int err, num_tx_queues = netdev->real_num_tx_queues;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> if (opt->parent_classid != TC_HTB_CLASSID_ROOT) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid parent classid");
> return -EINVAL;
> }
>
> - err = airoha_qdma_set_tx_rate_limit(dev, channel, rate, opt->quantum);
> + err = airoha_qdma_set_tx_rate_limit(netdev, channel, rate,
> + opt->quantum);
> if (err) {
> NL_SET_ERR_MSG_MOD(opt->extack,
> "failed configuring htb offload");
> @@ -2605,9 +2627,10 @@ static int airoha_tc_htb_alloc_leaf_queue(struct net_device *dev,
> if (opt->command == TC_HTB_NODE_MODIFY)
> return 0;
>
> - err = netif_set_real_num_tx_queues(dev, num_tx_queues + 1);
> + err = netif_set_real_num_tx_queues(netdev, num_tx_queues + 1);
> if (err) {
> - airoha_qdma_set_tx_rate_limit(dev, channel, 0, opt->quantum);
> + airoha_qdma_set_tx_rate_limit(netdev, channel, 0,
> + opt->quantum);
> NL_SET_ERR_MSG_MOD(opt->extack,
> "failed setting real_num_tx_queues");
> return err;
> @@ -2697,11 +2720,12 @@ static int airoha_tc_matchall_act_validate(struct tc_cls_matchall_offload *f)
> return 0;
> }
>
> -static int airoha_dev_tc_matchall(struct net_device *dev,
> +static int airoha_dev_tc_matchall(struct net_device *netdev,
> struct tc_cls_matchall_offload *f)
> {
> enum trtcm_unit_type unit_type = TRTCM_BYTE_UNIT;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> u32 rate = 0, bucket_size = 0;
>
> switch (f->command) {
> @@ -2736,18 +2760,19 @@ static int airoha_dev_tc_matchall(struct net_device *dev,
> static int airoha_dev_setup_tc_block_cb(enum tc_setup_type type,
> void *type_data, void *cb_priv)
> {
> - struct net_device *dev = cb_priv;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct net_device *netdev = cb_priv;
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> struct airoha_eth *eth = port->qdma->eth;
>
> - if (!tc_can_offload(dev))
> + if (!tc_can_offload(netdev))
> return -EOPNOTSUPP;
>
> switch (type) {
> case TC_SETUP_CLSFLOWER:
> return airoha_ppe_setup_tc_block_cb(ð->ppe->dev, type_data);
> case TC_SETUP_CLSMATCHALL:
> - return airoha_dev_tc_matchall(dev, type_data);
> + return airoha_dev_tc_matchall(netdev, type_data);
> default:
> return -EOPNOTSUPP;
> }
> @@ -2794,47 +2819,51 @@ static int airoha_dev_setup_tc_block(struct net_device *dev,
> }
> }
>
> -static void airoha_tc_remove_htb_queue(struct net_device *dev, int queue)
> +static void airoha_tc_remove_htb_queue(struct net_device *netdev, int queue)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> - netif_set_real_num_tx_queues(dev, dev->real_num_tx_queues - 1);
> - airoha_qdma_set_tx_rate_limit(dev, queue + 1, 0, 0);
> + netif_set_real_num_tx_queues(netdev, netdev->real_num_tx_queues - 1);
> + airoha_qdma_set_tx_rate_limit(netdev, queue + 1, 0, 0);
> clear_bit(queue, port->qos_sq_bmap);
> }
>
> -static int airoha_tc_htb_delete_leaf_queue(struct net_device *dev,
> +static int airoha_tc_htb_delete_leaf_queue(struct net_device *netdev,
> struct tc_htb_qopt_offload *opt)
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> if (!test_bit(channel, port->qos_sq_bmap)) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
> return -EINVAL;
> }
>
> - airoha_tc_remove_htb_queue(dev, channel);
> + airoha_tc_remove_htb_queue(netdev, channel);
>
> return 0;
> }
>
> -static int airoha_tc_htb_destroy(struct net_device *dev)
> +static int airoha_tc_htb_destroy(struct net_device *netdev)
> {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
> int q;
>
> for_each_set_bit(q, port->qos_sq_bmap, AIROHA_NUM_QOS_CHANNELS)
> - airoha_tc_remove_htb_queue(dev, q);
> + airoha_tc_remove_htb_queue(netdev, q);
>
> return 0;
> }
>
> -static int airoha_tc_get_htb_get_leaf_queue(struct net_device *dev,
> +static int airoha_tc_get_htb_get_leaf_queue(struct net_device *netdev,
> struct tc_htb_qopt_offload *opt)
> {
> u32 channel = TC_H_MIN(opt->classid) % AIROHA_NUM_QOS_CHANNELS;
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port = dev->port;
>
> if (!test_bit(channel, port->qos_sq_bmap)) {
> NL_SET_ERR_MSG_MOD(opt->extack, "invalid queue id");
> @@ -2870,8 +2899,8 @@ static int airoha_tc_setup_qdisc_htb(struct net_device *dev,
> return 0;
> }
>
> -static int airoha_dev_tc_setup(struct net_device *dev, enum tc_setup_type type,
> - void *type_data)
> +static int airoha_dev_tc_setup(struct net_device *dev,
> + enum tc_setup_type type, void *type_data)
> {
> switch (type) {
> case TC_SETUP_QDISC_ETS:
> @@ -2937,25 +2966,81 @@ static void airoha_metadata_dst_free(struct airoha_gdm_port *port)
> }
> }
>
> -bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
> - struct airoha_gdm_port *port)
> +bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
> + struct airoha_gdm_dev *dev)
> {
> int i;
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> - if (eth->ports[i] == port)
> + struct airoha_gdm_port *port = eth->ports[i];
> +
> + if (!port)
> + continue;
> +
> + if (port->dev == dev)
> return true;
> }
>
> return false;
> }
>
> +static int airoha_alloc_gdm_device(struct airoha_eth *eth,
> + struct airoha_gdm_port *port,
> + struct device_node *np)
> +{
> + struct airoha_gdm_dev *dev;
> + struct net_device *netdev;
> + int err;
> +
> + netdev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*dev),
> + AIROHA_NUM_NETDEV_TX_RINGS,
> + AIROHA_NUM_RX_RING);
> + if (!netdev) {
> + dev_err(eth->dev, "alloc_etherdev failed\n");
> + return -ENOMEM;
> + }
> +
> + netdev->netdev_ops = &airoha_netdev_ops;
> + netdev->ethtool_ops = &airoha_ethtool_ops;
> + netdev->max_mtu = AIROHA_MAX_MTU;
> + netdev->watchdog_timeo = 5 * HZ;
> + netdev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM | NETIF_F_TSO6 |
> + NETIF_F_IPV6_CSUM | NETIF_F_SG | NETIF_F_TSO |
> + NETIF_F_HW_TC;
> + netdev->features |= netdev->hw_features;
> + netdev->vlan_features = netdev->hw_features;
> + netdev->dev.of_node = np;
> + SET_NETDEV_DEV(netdev, eth->dev);
> +
> + /* reserve hw queues for HTB offloading */
> + err = netif_set_real_num_tx_queues(netdev, AIROHA_NUM_TX_RING);
> + if (err)
> + return err;
> +
> + err = of_get_ethdev_address(np, netdev);
> + if (err) {
> + if (err == -EPROBE_DEFER)
> + return err;
> +
> + eth_hw_addr_random(netdev);
> + dev_info(eth->dev, "generated random MAC address %pM\n",
> + netdev->dev_addr);
> + }
> +
> + dev = netdev_priv(netdev);
> + dev->dev = netdev;
> + dev->port = port;
> + port->dev = dev;
> + dev->eth = eth;
> +
> + return 0;
> +}
> +
> static int airoha_alloc_gdm_port(struct airoha_eth *eth,
> struct device_node *np)
> {
> const __be32 *id_ptr = of_get_property(np, "reg", NULL);
> struct airoha_gdm_port *port;
> - struct net_device *dev;
> int err, p;
> u32 id;
>
> @@ -2977,53 +3062,22 @@ static int airoha_alloc_gdm_port(struct airoha_eth *eth,
> return -EINVAL;
> }
>
> - dev = devm_alloc_etherdev_mqs(eth->dev, sizeof(*port),
> - AIROHA_NUM_NETDEV_TX_RINGS,
> - AIROHA_NUM_RX_RING);
> - if (!dev) {
> - dev_err(eth->dev, "alloc_etherdev failed\n");
> + port = devm_kzalloc(eth->dev, sizeof(*port), GFP_KERNEL);
> + if (!port)
> return -ENOMEM;
> - }
> -
> - dev->netdev_ops = &airoha_netdev_ops;
> - dev->ethtool_ops = &airoha_ethtool_ops;
> - dev->max_mtu = AIROHA_MAX_MTU;
> - dev->watchdog_timeo = 5 * HZ;
> - dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_RXCSUM |
> - NETIF_F_TSO6 | NETIF_F_IPV6_CSUM |
> - NETIF_F_SG | NETIF_F_TSO |
> - NETIF_F_HW_TC;
> - dev->features |= dev->hw_features;
> - dev->vlan_features = dev->hw_features;
> - dev->dev.of_node = np;
> - SET_NETDEV_DEV(dev, eth->dev);
> -
> - /* reserve hw queues for HTB offloading */
> - err = netif_set_real_num_tx_queues(dev, AIROHA_NUM_TX_RING);
> - if (err)
> - return err;
> -
> - err = of_get_ethdev_address(np, dev);
> - if (err) {
> - if (err == -EPROBE_DEFER)
> - return err;
> -
> - eth_hw_addr_random(dev);
> - dev_info(eth->dev, "generated random MAC address %pM\n",
> - dev->dev_addr);
> - }
>
> - port = netdev_priv(dev);
> u64_stats_init(&port->stats.syncp);
> spin_lock_init(&port->stats.lock);
> - port->eth = eth;
> - port->dev = dev;
> port->id = id;
> /* XXX: Read nbq from DTS */
> port->nbq = id == AIROHA_GDM3_IDX && airoha_is_7581(eth) ? 4 : 0;
> eth->ports[p] = port;
>
> - return airoha_metadata_dst_alloc(port);
> + err = airoha_metadata_dst_alloc(port);
> + if (err)
> + return err;
> +
> + return airoha_alloc_gdm_device(eth, port, np);
> }
>
> static int airoha_register_gdm_devices(struct airoha_eth *eth)
> @@ -3037,7 +3091,7 @@ static int airoha_register_gdm_devices(struct airoha_eth *eth)
> if (!port)
> continue;
>
> - err = register_netdev(port->dev);
> + err = register_netdev(port->dev->dev);
> if (err)
> return err;
> }
> @@ -3146,12 +3200,14 @@ static int airoha_probe(struct platform_device *pdev)
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> struct airoha_gdm_port *port = eth->ports[i];
> + struct airoha_gdm_dev *dev;
>
> if (!port)
> continue;
>
> - if (port->dev->reg_state == NETREG_REGISTERED)
> - unregister_netdev(port->dev);
> + dev = port->dev;
> + if (dev && dev->dev->reg_state == NETREG_REGISTERED)
> + unregister_netdev(dev->dev);
> airoha_metadata_dst_free(port);
> }
> airoha_hw_cleanup(eth);
> @@ -3172,11 +3228,14 @@ static void airoha_remove(struct platform_device *pdev)
>
> for (i = 0; i < ARRAY_SIZE(eth->ports); i++) {
> struct airoha_gdm_port *port = eth->ports[i];
> + struct airoha_gdm_dev *dev;
>
> if (!port)
> continue;
>
> - unregister_netdev(port->dev);
> + dev = port->dev;
> + if (dev)
> + unregister_netdev(dev->dev);
> airoha_metadata_dst_free(port);
> }
> airoha_hw_cleanup(eth);
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.h b/drivers/net/ethernet/airoha/airoha_eth.h
> index d3781103abb5..c78cabbec753 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.h
> +++ b/drivers/net/ethernet/airoha/airoha_eth.h
> @@ -535,10 +535,15 @@ struct airoha_qdma {
> struct airoha_queue q_rx[AIROHA_NUM_RX_RING];
> };
>
> +struct airoha_gdm_dev {
> + struct airoha_gdm_port *port;
> + struct net_device *dev;
> + struct airoha_eth *eth;
> +};
> +
> struct airoha_gdm_port {
> struct airoha_qdma *qdma;
> - struct airoha_eth *eth;
> - struct net_device *dev;
> + struct airoha_gdm_dev *dev;
> int id;
> int nbq;
>
> @@ -662,8 +667,8 @@ static inline bool airoha_is_7583(struct airoha_eth *eth)
> }
>
> int airoha_get_fe_port(struct airoha_gdm_port *port);
> -bool airoha_is_valid_gdm_port(struct airoha_eth *eth,
> - struct airoha_gdm_port *port);
> +bool airoha_is_valid_gdm_dev(struct airoha_eth *eth,
> + struct airoha_gdm_dev *dev);
>
> void airoha_ppe_set_cpu_port(struct airoha_gdm_port *port, u8 ppe_id,
> u8 fport);
> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> index 26da519236bf..af7af4097b98 100644
> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> @@ -298,12 +298,12 @@ static void airoha_ppe_foe_set_bridge_addrs(struct airoha_foe_bridge *br,
>
> static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
> struct airoha_foe_entry *hwe,
> - struct net_device *dev, int type,
> + struct net_device *netdev, int type,
> struct airoha_flow_data *data,
> int l4proto)
> {
> u32 qdata = FIELD_PREP(AIROHA_FOE_SHAPER_ID, 0x7f), ports_pad, val;
> - int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&dev);
> + int wlan_etype = -EINVAL, dsa_port = airoha_get_dsa_port(&netdev);
> struct airoha_foe_mac_info_common *l2;
> u8 smac_id = 0xf;
>
> @@ -319,10 +319,11 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
> hwe->ib1 = val;
>
> val = FIELD_PREP(AIROHA_FOE_IB2_PORT_AG, 0x1f);
> - if (dev) {
> + if (netdev) {
> struct airoha_wdma_info info = {};
>
> - if (!airoha_ppe_get_wdma_info(dev, data->eth.h_dest, &info)) {
> + if (!airoha_ppe_get_wdma_info(netdev, data->eth.h_dest,
> + &info)) {
> val |= FIELD_PREP(AIROHA_FOE_IB2_NBQ, info.idx) |
> FIELD_PREP(AIROHA_FOE_IB2_PSE_PORT,
> FE_PSE_PORT_CDM4);
> @@ -332,12 +333,14 @@ static int airoha_ppe_foe_entry_prepare(struct airoha_eth *eth,
> FIELD_PREP(AIROHA_FOE_MAC_WDMA_WCID,
> info.wcid);
> } else {
> - struct airoha_gdm_port *port = netdev_priv(dev);
> + struct airoha_gdm_dev *dev = netdev_priv(netdev);
> + struct airoha_gdm_port *port;
> u8 pse_port, channel;
>
> - if (!airoha_is_valid_gdm_port(eth, port))
> + if (!airoha_is_valid_gdm_dev(eth, dev))
> return -EINVAL;
>
> + port = dev->port;
> if (dsa_port >= 0 || eth->ports[1])
> pse_port = port->id == 4 ? FE_PSE_PORT_GDM4
> : port->id;
> @@ -1473,7 +1476,7 @@ void airoha_ppe_check_skb(struct airoha_ppe_dev *dev, struct sk_buff *skb,
> void airoha_ppe_init_upd_mem(struct airoha_gdm_port *port)
> {
> struct airoha_eth *eth = port->qdma->eth;
> - struct net_device *dev = port->dev;
> + struct net_device *dev = port->dev->dev;
> const u8 *addr = dev->dev_addr;
> u32 val;
>
>
> --
> 2.54.0
>
Commenting on sashiko's report:
https://sashiko.dev/#/patchset/20260511-airoha-eth-multi-serdes-v6-0-c899462c4f75%40kernel.org
- This problem wasn't introduced by this patch, but does this function call lead
to out-of-bounds memory accesses?
- I do not think this is an issue, since airoha_eth driver supports just mtk as
dsa driver where we actually skb_push() MTK_HDR_LEN bytes in mtk_tag_xmit().
- This problem wasn't introduced by this patch, but does this incorrect bounds
accounting in HTB offload break QoS?
- I will fix this issue with a dedicated patch.
- This problem wasn't introduced by this patch, but does this leave the rate
limit active due to an off-by-one error?
- I will fix this issue with a dedicated patch.
- This problem wasn't introduced by this patch, but does this lead to a
use-after-free of the device tree node?
- This is fixed by a subsequent patch in the series.
Regards,
Lorenzo
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2 6/8] PCI: aardvark: Add 100 ms delay after link training
From: Pali Rohár @ 2026-05-12 21:25 UTC (permalink / raw)
To: Hans Zhang
Cc: bhelgaas, lpieralisi, kwilczynski, mani, vigneshr, jingoohan1,
thomas.petazzoni, ryder.lee, jianjun.wang, claudiu.beznea.uj,
mpillai, robh, s-vadapalli, linux-omap, linux-arm-kernel,
linux-mediatek, linux-renesas-soc, linux-pci, linux-kernel
In-Reply-To: <20260506152346.166056-7-18255117159@163.com>
On Wednesday 06 May 2026 23:23:44 Hans Zhang wrote:
> The Aardvark PCIe controller driver waits for the link to come up but
> does not implement the mandatory 100 ms delay after link training
> completes for speeds greater than 5.0 GT/s (PCIe r6.0 sec 6.6.1).
>
> The driver already maintains a 'link_gen' field that holds the negotiated
> link speed. Use it together with pcie_wait_after_link_train() to insert
> the required delay immediately after confirming that the link is up.
>
> Signed-off-by: Hans Zhang <18255117159@163.com>
> ---
> drivers/pci/controller/pci-aardvark.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
> index e34bea1ff0ac..526351c21c49 100644
> --- a/drivers/pci/controller/pci-aardvark.c
> +++ b/drivers/pci/controller/pci-aardvark.c
> @@ -350,8 +350,10 @@ static int advk_pcie_wait_for_link(struct advk_pcie *pcie)
>
> /* check if the link is up or not */
> for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) {
> - if (advk_pcie_link_up(pcie))
> + if (advk_pcie_link_up(pcie)) {
> + pcie_wait_after_link_train(pcie->link_gen);
> return 0;
> + }
>
> usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX);
> }
> --
> 2.34.1
>
Are you sure that this is correct to do? Have you checked the A3720
Functional Specification which describes how to bring PCIe link up?
A3720 PCIe controller is buggy and needs more timing hacks to make it
behave. Playing with random sleeps can break its internal logic.
I'm not sure if it could be safe without proper testing.
And IIRC A3720 PCIe controller is just PCIe2.0 with 5 GT/s.
^ permalink raw reply
* [PATCH 1/1] dt-bindings: display: imx: Add television encoder (TVE) for imx53
From: Frank Li @ 2026-05-12 22:31 UTC (permalink / raw)
To: Philipp Zabel, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam,
open list:DRM DRIVERS FOR FREESCALE IMX 5/6,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
open list
Cc: imx
Add television encoder (TVE) for legacy i.MX53 (over 15 years) to fix below
DTB_CHECK warnings:
arch/arm/boot/dts/nxp/imx/imx53-ard.dtb: /soc/bus@60000000/tve@63ff0000: failed to match any schema with compatible: ['fsl,imx53-tve']
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
About cleanup 300 lines warnings for i.MX ARM platform
---
.../bindings/display/imx/fsl,imx53-tve.yaml | 102 ++++++++++++++++++
1 file changed, 102 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx53-tve.yaml
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx53-tve.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx53-tve.yaml
new file mode 100644
index 0000000000000..a7c971be1959b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx53-tve.yaml
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx53-tve.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX53 Television Encoder (TVE)
+
+maintainers:
+ - Frank Li <Frank.Li@nxp.com>
+
+description:
+ The Television Encoder (TVE) is a hardware block in the i.MX53 SoC that
+ converts digital video data into analog TV signals (NTSC/PAL).
+
+properties:
+ compatible:
+ const: fsl,imx53-tve
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: TVE gate clock
+ - description: Display interface selector clock
+
+ clock-names:
+ items:
+ - const: tve
+ - const: di_sel
+
+ ddc-i2c-bus:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the I2C bus used for DDC (Display Data Channel) communication
+ to read EDID information from the connected display.
+
+ dac-supply:
+ description:
+ Regulator supply for the TVE DAC (Digital-to-Analog Converter).
+
+ fsl,tve-mode:
+ $ref: /schemas/types.yaml#/definitions/string
+ description:
+ TVE output mode selection.
+ enum:
+ - ntsc
+ - pal
+ - vga
+
+ fsl,hsync-pin:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Pin number for horizontal sync signal in VGA mode.
+ minimum: 0
+ maximum: 8
+
+ fsl,vsync-pin:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Pin number for vertical sync signal in VGA mode.
+ minimum: 0
+ maximum: 8
+
+ port:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ Port node with one endpoint connected to the IPU display interface.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/imx5-clock.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ tve@63ff0000 {
+ compatible = "fsl,imx53-tve";
+ reg = <0x63ff0000 0x1000>;
+ interrupts = <92>;
+ clocks = <&clks IMX5_CLK_TVE_GATE>,
+ <&clks IMX5_CLK_IPU_DI1_SEL>;
+ clock-names = "tve", "di_sel";
+
+ port {
+ endpoint {
+ remote-endpoint = <&ipu_di1_tve>;
+ };
+ };
+ };
+
--
2.43.0
^ permalink raw reply related
* [PATCH 00/12] crypto: atmel - refactor common i2c support and add SHA256 ahash support
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
This series restructures the Atmel secure element drivers around a
shared atmel-i2c core and adds SHA256 ahash support for ATSHA204A and
ECC based devices.
The existing drivers duplicated substantial parts of the transport,
RNG, EEPROM and device management logic. This series consolidates the
common functionality into the shared i2c core and converts the client
drivers to capability based allocation.
The series also introduces per-device timing configuration through
match data, moves sanity checks and RNG handling into the core driver,
updates workqueue handling and cleans up internal constants and helper
definitions.
The final patch adds SHA256 ahash support using the hardware SHA engine
provided by the devices.
ATSHA204A devices require software-side SHA256 padding according to
FIPS 180-4, while newer ECC devices provide a dedicated SHA final
command and perform padding internally in hardware.
Supporting the SHA engine also requires changes to the command
transport path. SHA operations must execute as a strict uninterrupted
sequence consisting of SHA INIT, one or more SHA COMPUTE commands and,
for ECC devices, a terminating SHA FINAL command. The device loses its
internal SHA state if it enters sleep mode or if unrelated commands
are interleaved during the transaction.
To satisfy these hardware requirements, the send/receive path is split
into a low-level transfer helper and a higher-level wrapper managing
wakeup, sleep and locking. SHA operations keep the device awake and
hold the i2c lock for the full duration of the hashing transaction.
The series has been tested on ATSHA204A and ATECC508A devices.
Tests are ongoing/pending on ATECC608A and ATECC608B.
---
Lothar Rubusch (12):
crypto: atmel - introduce shared I2C client management
crypto: atmel - move capability-based client allocation into i2c core
crypto: atmel - remove obsolete CONFIG_OF guard
crypto: atmel - add per-device timing and match-data driven
configuration
crypto: atmel - move RNG support into common i2c core
crypto: atmel - move EEPROM access support into common i2c core
crypto: atmel - expose CONFIG zone through sysfs
crypto: atmel - move device sanity check to core driver
crypto: atmel - check client data in remove callbacks
crypto: atmel - update workqueue flags and add flush on exit
crypto: atmel - refactor and localize driver constants
crypto: atmel - add SHA256 ahash support
drivers/crypto/atmel-ecc.c | 252 +++++++-----
drivers/crypto/atmel-i2c.c | 679 +++++++++++++++++++++++++++++----
drivers/crypto/atmel-i2c.h | 180 +++++----
drivers/crypto/atmel-sha204a.c | 284 +++++++-------
4 files changed, 1010 insertions(+), 385 deletions(-)
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
base-commit: f7dd32c5179d7755de18e21d5674b08f9e5cb180
--
2.53.0
^ permalink raw reply
* [PATCH 02/12] crypto: atmel - move capability-based client allocation into i2c core
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Move the i2c client allocation logic from atmel-ecc into the shared
atmel-i2c core and extend it to support capability-based client
selection.
Introduce enum atmel_i2c_capability and add capability flags to
struct atmel_i2c_client_priv. Devices now advertise their supported
features during probe, allowing atmel_i2c_client_alloc() to select a
compatible client from the shared i2c client list.
The allocation logic continues to balance crypto transformation usage
across devices by selecting the client with the lowest tfm_count, but
is no longer limited to ECC-capable devices.
This centralizes shared client management in the common atmel-i2c core
and prepares the infrastructure for additional shared crypto features
across compatible Atmel devices.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 39 +++-------------------------------
drivers/crypto/atmel-i2c.c | 39 ++++++++++++++++++++++++++++++++++
drivers/crypto/atmel-i2c.h | 7 ++++++
drivers/crypto/atmel-sha204a.c | 2 ++
4 files changed, 51 insertions(+), 36 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index cba4238735cc..c63d30947bd7 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -200,41 +200,6 @@ static int atmel_ecdh_compute_shared_secret(struct kpp_request *req)
return ret;
}
-static struct i2c_client *atmel_ecc_i2c_client_alloc(void)
-{
- struct atmel_i2c_client_priv *i2c_priv, *min_i2c_priv = NULL;
- struct i2c_client *client = ERR_PTR(-ENODEV);
- int min_tfm_cnt = INT_MAX;
- int tfm_cnt;
-
- spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
-
- if (list_empty(&atmel_i2c_mgmt.i2c_client_list)) {
- spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
- return ERR_PTR(-ENODEV);
- }
-
- list_for_each_entry(i2c_priv, &atmel_i2c_mgmt.i2c_client_list,
- i2c_client_list_node) {
- tfm_cnt = atomic_read(&i2c_priv->tfm_count);
- if (tfm_cnt < min_tfm_cnt) {
- min_tfm_cnt = tfm_cnt;
- min_i2c_priv = i2c_priv;
- }
- if (!min_tfm_cnt)
- break;
- }
-
- if (min_i2c_priv) {
- atomic_inc(&min_i2c_priv->tfm_count);
- client = min_i2c_priv->client;
- }
-
- spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
-
- return client;
-}
-
static void atmel_ecc_i2c_client_free(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
@@ -249,7 +214,7 @@ static int atmel_ecdh_init_tfm(struct crypto_kpp *tfm)
struct atmel_ecdh_ctx *ctx = kpp_tfm_ctx(tfm);
ctx->curve_id = ECC_CURVE_NIST_P256;
- ctx->client = atmel_ecc_i2c_client_alloc();
+ ctx->client = atmel_i2c_client_alloc(ATMEL_CAP_ECDH);
if (IS_ERR(ctx->client)) {
pr_err("tfm - i2c_client binding failed\n");
return PTR_ERR(ctx->client);
@@ -321,6 +286,8 @@ static int atmel_ecc_probe(struct i2c_client *client)
i2c_priv = i2c_get_clientdata(client);
+ i2c_priv->caps = BIT(ATMEL_CAP_ECDH);
+
/* add to client list */
spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
list_add_tail(&i2c_priv->i2c_client_list_node,
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index 861af52d7a88..b7ee2ec37531 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -57,6 +57,45 @@ static void atmel_i2c_checksum(struct atmel_i2c_cmd *cmd)
*__crc16 = cpu_to_le16(bitrev16(crc16(0, data, len)));
}
+struct i2c_client *atmel_i2c_client_alloc(enum atmel_i2c_capability cap)
+{
+ struct atmel_i2c_client_priv *i2c_priv, *min_i2c_priv = NULL;
+ struct i2c_client *client = ERR_PTR(-ENODEV);
+ int min_tfm_cnt = INT_MAX;
+ int tfm_cnt;
+
+ spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
+
+ if (list_empty(&atmel_i2c_mgmt.i2c_client_list)) {
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+ return ERR_PTR(-ENODEV);
+ }
+
+ list_for_each_entry(i2c_priv, &atmel_i2c_mgmt.i2c_client_list,
+ i2c_client_list_node) {
+ if (!(i2c_priv->caps & BIT(cap)))
+ continue;
+
+ tfm_cnt = atomic_read(&i2c_priv->tfm_count);
+ if (tfm_cnt < min_tfm_cnt) {
+ min_tfm_cnt = tfm_cnt;
+ min_i2c_priv = i2c_priv;
+ }
+ if (!min_tfm_cnt)
+ break;
+ }
+
+ if (min_i2c_priv) {
+ atomic_inc(&min_i2c_priv->tfm_count);
+ client = min_i2c_priv->client;
+ }
+
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+
+ return client;
+}
+EXPORT_SYMBOL(atmel_i2c_client_alloc);
+
void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd)
{
cmd->word_addr = COMMAND;
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index 43a0c1cfcd94..70579b438256 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -115,6 +115,10 @@ struct atmel_i2c_cmd {
#define ECDH_PREFIX_MODE 0x00
/* Used for binding tfm objects to i2c clients. */
+enum atmel_i2c_capability {
+ ATMEL_CAP_ECDH = 0,
+};
+
struct atmel_i2c_client_mgmt {
struct list_head i2c_client_list;
spinlock_t i2c_list_lock;
@@ -130,6 +134,7 @@ extern struct atmel_i2c_client_mgmt atmel_i2c_mgmt;
* @wake_token_sz : size in bytes of the wake_token
* @tfm_count : number of active crypto transformations on i2c client
* @hwrng : hold the hardware generated rng
+ * @caps : feature capability of the particular driver
*
* Reads and writes from/to the i2c client are sequential. The first byte
* transmitted to the device is treated as the byte size. Any attempt to send
@@ -146,6 +151,7 @@ struct atmel_i2c_client_priv {
size_t wake_token_sz;
atomic_t tfm_count ____cacheline_aligned;
struct hwrng hwrng;
+ u32 caps;
};
/**
@@ -190,6 +196,7 @@ void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid);
int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
struct scatterlist *pubkey);
+struct i2c_client *atmel_i2c_client_alloc(enum atmel_i2c_capability cap);
void atmel_i2c_unregister_client(struct atmel_i2c_client_priv *i2c_priv);
#endif /* __ATMEL_I2C_H__ */
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index e6808c2bc891..ab758c9cd410 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -173,6 +173,8 @@ static int atmel_sha204a_probe(struct i2c_client *client)
i2c_priv = i2c_get_clientdata(client);
+ i2c_priv->caps = 0;
+
/* add to client list */
spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
list_add_tail(&i2c_priv->i2c_client_list_node,
--
2.53.0
^ permalink raw reply related
* [PATCH 01/12] crypto: atmel - introduce shared I2C client management
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Introduce a shared I2C client management infrastructure in the
atmel-i2c core and convert the atmel-ecc and atmel-sha204a drivers
to use it.
Replace the driver-local atmel_ecc_driver_data structure with the
common atmel_i2c_mgmt instance, providing a shared client list and
locking for compatible Atmel secure element devices. Add a common
atmel_i2c_unregister_client() helper to centralize client removal
handling.
Refactor both drivers to use module_i2c_driver() and move duplicated
client list handling into the shared infrastructure. Probe and remove
paths are updated accordingly, including consistent error unwinding
for client registration failures.
Subsequent patches will build on the shared client infrastructure.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 58 ++++++++++++++--------------------
drivers/crypto/atmel-i2c.c | 15 +++++++++
drivers/crypto/atmel-i2c.h | 5 ++-
drivers/crypto/atmel-sha204a.c | 38 ++++++++++++----------
4 files changed, 65 insertions(+), 51 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index 3738a4eb8701..cba4238735cc 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -23,8 +23,6 @@
#include <crypto/kpp.h>
#include "atmel-i2c.h"
-static struct atmel_ecc_driver_data driver_data;
-
/**
* struct atmel_ecdh_ctx - transformation context
* @client : pointer to i2c client device
@@ -209,14 +207,14 @@ static struct i2c_client *atmel_ecc_i2c_client_alloc(void)
int min_tfm_cnt = INT_MAX;
int tfm_cnt;
- spin_lock(&driver_data.i2c_list_lock);
+ spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
- if (list_empty(&driver_data.i2c_client_list)) {
- spin_unlock(&driver_data.i2c_list_lock);
+ if (list_empty(&atmel_i2c_mgmt.i2c_client_list)) {
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
return ERR_PTR(-ENODEV);
}
- list_for_each_entry(i2c_priv, &driver_data.i2c_client_list,
+ list_for_each_entry(i2c_priv, &atmel_i2c_mgmt.i2c_client_list,
i2c_client_list_node) {
tfm_cnt = atomic_read(&i2c_priv->tfm_count);
if (tfm_cnt < min_tfm_cnt) {
@@ -232,7 +230,7 @@ static struct i2c_client *atmel_ecc_i2c_client_alloc(void)
client = min_i2c_priv->client;
}
- spin_unlock(&driver_data.i2c_list_lock);
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
return client;
}
@@ -319,27 +317,34 @@ static int atmel_ecc_probe(struct i2c_client *client)
ret = atmel_i2c_probe(client);
if (ret)
- return ret;
+ goto done;
i2c_priv = i2c_get_clientdata(client);
- spin_lock(&driver_data.i2c_list_lock);
+ /* add to client list */
+ spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
list_add_tail(&i2c_priv->i2c_client_list_node,
- &driver_data.i2c_client_list);
- spin_unlock(&driver_data.i2c_list_lock);
+ &atmel_i2c_mgmt.i2c_client_list);
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+ /* register algorithms */
ret = crypto_register_kpp(&atmel_ecdh_nist_p256);
if (ret) {
- spin_lock(&driver_data.i2c_list_lock);
- list_del(&i2c_priv->i2c_client_list_node);
- spin_unlock(&driver_data.i2c_list_lock);
-
dev_err(&client->dev, "%s alg registration failed\n",
atmel_ecdh_nist_p256.base.cra_driver_name);
+ goto err_list_del;
} else {
dev_info(&client->dev, "atmel ecc algorithms registered in /proc/crypto\n");
}
+ goto done;
+
+err_list_del:
+ spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
+ list_del(&i2c_priv->i2c_client_list_node);
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+
+done:
return ret;
}
@@ -361,11 +366,10 @@ static void atmel_ecc_remove(struct i2c_client *client)
return;
}
- crypto_unregister_kpp(&atmel_ecdh_nist_p256);
+ atmel_i2c_unregister_client(i2c_priv);
+ atmel_i2c_flush_queue();
- spin_lock(&driver_data.i2c_list_lock);
- list_del(&i2c_priv->i2c_client_list_node);
- spin_unlock(&driver_data.i2c_list_lock);
+ crypto_unregister_kpp(&atmel_ecdh_nist_p256);
}
#ifdef CONFIG_OF
@@ -398,21 +402,7 @@ static struct i2c_driver atmel_ecc_driver = {
.id_table = atmel_ecc_id,
};
-static int __init atmel_ecc_init(void)
-{
- spin_lock_init(&driver_data.i2c_list_lock);
- INIT_LIST_HEAD(&driver_data.i2c_client_list);
- return i2c_add_driver(&atmel_ecc_driver);
-}
-
-static void __exit atmel_ecc_exit(void)
-{
- atmel_i2c_flush_queue();
- i2c_del_driver(&atmel_ecc_driver);
-}
-
-module_init(atmel_ecc_init);
-module_exit(atmel_ecc_exit);
+module_i2c_driver(atmel_ecc_driver);
MODULE_AUTHOR("Tudor Ambarus");
MODULE_DESCRIPTION("Microchip / Atmel ECC (I2C) driver");
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index 0e275dbdc8c5..861af52d7a88 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -21,6 +21,12 @@
#include <linux/workqueue.h>
#include "atmel-i2c.h"
+struct atmel_i2c_client_mgmt atmel_i2c_mgmt = {
+ .i2c_list_lock = __SPIN_LOCK_UNLOCKED(atmel_i2c_mgmt.i2c_list_lock),
+ .i2c_client_list = LIST_HEAD_INIT(atmel_i2c_mgmt.i2c_client_list),
+};
+EXPORT_SYMBOL_GPL(atmel_i2c_mgmt);
+
static const struct {
u8 value;
const char *error_text;
@@ -348,6 +354,15 @@ static int device_sanity_check(struct i2c_client *client)
return ret;
}
+void atmel_i2c_unregister_client(struct atmel_i2c_client_priv *i2c_priv)
+{
+ spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
+ if (!list_empty(&i2c_priv->i2c_client_list_node))
+ list_del_init(&i2c_priv->i2c_client_list_node);
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+}
+EXPORT_SYMBOL(atmel_i2c_unregister_client);
+
int atmel_i2c_probe(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv;
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index 72f04c15682f..43a0c1cfcd94 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -115,10 +115,11 @@ struct atmel_i2c_cmd {
#define ECDH_PREFIX_MODE 0x00
/* Used for binding tfm objects to i2c clients. */
-struct atmel_ecc_driver_data {
+struct atmel_i2c_client_mgmt {
struct list_head i2c_client_list;
spinlock_t i2c_list_lock;
} ____cacheline_aligned;
+extern struct atmel_i2c_client_mgmt atmel_i2c_mgmt;
/**
* atmel_i2c_client_priv - i2c_client private data
@@ -189,4 +190,6 @@ void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid);
int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
struct scatterlist *pubkey);
+void atmel_i2c_unregister_client(struct atmel_i2c_client_priv *i2c_priv);
+
#endif /* __ATMEL_I2C_H__ */
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index ed7d69bf6890..e6808c2bc891 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -169,10 +169,17 @@ static int atmel_sha204a_probe(struct i2c_client *client)
ret = atmel_i2c_probe(client);
if (ret)
- return ret;
+ goto done;
i2c_priv = i2c_get_clientdata(client);
+ /* add to client list */
+ spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
+ list_add_tail(&i2c_priv->i2c_client_list_node,
+ &atmel_i2c_mgmt.i2c_client_list);
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+
+ /* register rng */
memset(&i2c_priv->hwrng, 0, sizeof(i2c_priv->hwrng));
i2c_priv->hwrng.name = dev_name(&client->dev);
@@ -183,15 +190,26 @@ static int atmel_sha204a_probe(struct i2c_client *client)
i2c_priv->hwrng.quality = *quality;
ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng);
- if (ret)
+ if (ret) {
dev_warn(&client->dev, "failed to register RNG (%d)\n", ret);
+ goto err_list_del;
+ }
ret = sysfs_create_group(&client->dev.kobj, &atmel_sha204a_groups);
if (ret) {
dev_err(&client->dev, "failed to register sysfs entry\n");
- return ret;
+ goto err_list_del;
}
+ goto done;
+
+err_list_del:
+ sysfs_remove_group(&client->dev.kobj, &atmel_sha204a_groups);
+ spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
+ list_del(&i2c_priv->i2c_client_list_node);
+ spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+
+done:
return ret;
}
@@ -230,19 +248,7 @@ static struct i2c_driver atmel_sha204a_driver = {
.driver.of_match_table = of_match_ptr(atmel_sha204a_dt_ids),
};
-static int __init atmel_sha204a_init(void)
-{
- return i2c_add_driver(&atmel_sha204a_driver);
-}
-
-static void __exit atmel_sha204a_exit(void)
-{
- atmel_i2c_flush_queue();
- i2c_del_driver(&atmel_sha204a_driver);
-}
-
-module_init(atmel_sha204a_init);
-module_exit(atmel_sha204a_exit);
+module_i2c_driver(atmel_sha204a_driver);
MODULE_AUTHOR("Ard Biesheuvel <ard.biesheuvel@linaro.org>");
MODULE_DESCRIPTION("Microchip / Atmel SHA204A (I2C) driver");
--
2.53.0
^ permalink raw reply related
* [PATCH 03/12] crypto: atmel - remove obsolete CONFIG_OF guard
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Remove the CONFIG_OF preprocessor guard around the OF device match
table in atmel-ecc.
OF match tables are expected to be present unconditionally and the
MODULE_DEVICE_TABLE(of, ...) handling already accounts for
configurations where OF support is disabled. Keeping the additional
guard provides no benefit and only adds unnecessary conditional
compilation.
Also compact the match table formatting while touching the code.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index c63d30947bd7..0dede3707b73 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -339,18 +339,12 @@ static void atmel_ecc_remove(struct i2c_client *client)
crypto_unregister_kpp(&atmel_ecdh_nist_p256);
}
-#ifdef CONFIG_OF
static const struct of_device_id atmel_ecc_dt_ids[] = {
- {
- .compatible = "atmel,atecc508a",
- }, {
- .compatible = "atmel,atecc608b",
- }, {
- /* sentinel */
- }
+ { .compatible = "atmel,atecc508a", },
+ { .compatible = "atmel,atecc608b", },
+ { }
};
MODULE_DEVICE_TABLE(of, atmel_ecc_dt_ids);
-#endif
static const struct i2c_device_id atmel_ecc_id[] = {
{ "atecc508a" },
--
2.53.0
^ permalink raw reply related
* [PATCH 08/12] crypto: atmel - move device sanity check to core driver
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Move the device sanity check implementation into the shared Atmel
I2C core driver and reuse the generic EEPROM access helpers for
reading the CONFIG zone lock state.
This removes duplicate CONFIG zone handling and consolidates common
response index and lock state definitions under the Atmel I2C core
namespace.
Update both SHA204A and ECC drivers to invoke the shared sanity
check helper during probe.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 10 ++++++--
drivers/crypto/atmel-i2c.c | 43 ++++++++++++----------------------
drivers/crypto/atmel-i2c.h | 14 +++--------
drivers/crypto/atmel-sha204a.c | 6 +++++
4 files changed, 32 insertions(+), 41 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index f08fdf284b60..f6d1a9694d63 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -81,7 +81,7 @@ static void atmel_ecdh_done(struct atmel_i2c_work_data *work_data, void *areq,
/* copy the shared secret */
copied = sg_copy_from_buffer(req->dst, sg_nents_for_len(req->dst, n_sz),
- &cmd->data[RSP_DATA_IDX], n_sz);
+ &cmd->data[ATMEL_I2C_RSP_DATA_IDX], n_sz);
if (copied != n_sz)
status = -EINVAL;
@@ -144,7 +144,7 @@ static int atmel_ecdh_set_secret(struct crypto_kpp *tfm, const void *buf,
goto free_public_key;
/* save the public key */
- memcpy(public_key, &cmd->data[RSP_DATA_IDX], ATMEL_ECC_PUBKEY_SIZE);
+ memcpy(public_key, &cmd->data[ATMEL_I2C_RSP_DATA_IDX], ATMEL_ECC_PUBKEY_SIZE);
ctx->public_key = public_key;
kfree(cmd);
@@ -323,6 +323,12 @@ static int atmel_ecc_probe(struct i2c_client *client)
i2c_priv->data = data;
i2c_priv->caps = BIT(ATMEL_CAP_ECDH);
+ ret = atmel_i2c_device_sanity_check(client);
+ if (ret) {
+ dev_err(&client->dev, "failed to read EEPROM, is hardware attached?\n");
+ goto done;
+ }
+
/* add to client list */
spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
list_add_tail(&i2c_priv->i2c_client_list_node,
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index 26863573a10f..50b6bce478d2 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -23,6 +23,11 @@
#define ATMEL_I2C_COMMAND 0x03 /* packet function */
+/* Definitions for the device lock state */
+#define ATMEL_I2C_DEVICE_LOCK_ADDR 0x15
+#define ATMEL_I2C_LOCK_VALUE_IDX (ATMEL_I2C_RSP_DATA_IDX + 2)
+#define ATMEL_I2C_LOCK_CONFIG_IDX (ATMEL_I2C_RSP_DATA_IDX + 3)
+
/* Command opcode */
#define ATMEL_I2C_OPCODE_ECDH 0x43
#define ATMEL_I2C_OPCODE_GENKEY 0x40
@@ -129,26 +134,6 @@ static int atmel_i2c_init_read_eeprom_cmd(struct atmel_i2c_cmd *cmd, u16 addr,
return 0;
}
-void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd,
- const struct atmel_i2c_max_exec_timings *timings)
-{
- cmd->word_addr = ATMEL_I2C_COMMAND;
- cmd->opcode = ATMEL_I2C_OPCODE_READ;
- /*
- * Read the word from Configuration zone that contains the lock bytes
- * (UserExtra, Selector, LockValue, LockConfig).
- */
- cmd->param1 = CONFIGURATION_ZONE;
- cmd->param2 = cpu_to_le16(DEVICE_LOCK_ADDR);
- cmd->count = ATMEL_I2C_READ_COUNT;
-
- atmel_i2c_checksum(cmd);
-
- cmd->msecs = timings->max_exec_time_read;
- cmd->rxsize = ATMEL_I2C_READ_RSP_SIZE;
-}
-EXPORT_SYMBOL(atmel_i2c_init_read_config_cmd);
-
void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd,
const struct atmel_i2c_max_exec_timings *timings)
{
@@ -247,7 +232,7 @@ static int atmel_i2c_rng_read_nonblocking(struct hwrng *rng, void *buf,
if (rng->priv) {
work_data = (struct atmel_i2c_work_data *)rng->priv;
max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
- memcpy(buf, &work_data->cmd.data[RSP_DATA_IDX], max);
+ memcpy(buf, &work_data->cmd.data[ATMEL_I2C_RSP_DATA_IDX], max);
rng->priv = 0;
} else {
work_data = kmalloc_obj(*work_data, GFP_ATOMIC);
@@ -287,7 +272,7 @@ static int atmel_i2c_rng_read(struct hwrng *rng, void *buf, size_t max,
return ret;
max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
- memcpy(buf, &cmd.data[RSP_DATA_IDX], max);
+ memcpy(buf, &cmd.data[ATMEL_I2C_RSP_DATA_IDX], max);
return max;
}
@@ -338,7 +323,7 @@ static int atmel_i2c_eeprom_read(struct i2c_client *client, u16 addr,
goto err;
}
- memcpy(buf, cmd->data + RSP_DATA_IDX, 4);
+ memcpy(buf, cmd->data + ATMEL_I2C_RSP_DATA_IDX, 4);
err:
kfree(cmd);
@@ -542,7 +527,7 @@ static inline size_t atmel_i2c_wake_token_sz(u32 bus_clk_rate)
return DIV_ROUND_UP(no_of_bits, 8);
}
-static int device_sanity_check(struct i2c_client *client)
+int atmel_i2c_device_sanity_check(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
const struct atmel_i2c_of_match_data *data = i2c_priv->data;
@@ -553,7 +538,8 @@ static int device_sanity_check(struct i2c_client *client)
if (!cmd)
return -ENOMEM;
- atmel_i2c_init_read_config_cmd(cmd, &data->timings);
+ atmel_i2c_init_read_eeprom_cmd(cmd, ATMEL_I2C_DEVICE_LOCK_ADDR,
+ ATMEL_EEPROM_CONFIG_ZONE, data);
ret = atmel_i2c_send_receive(client, cmd);
if (ret)
@@ -565,8 +551,8 @@ static int device_sanity_check(struct i2c_client *client)
* Failure to lock these zones may permit modification of any secret
* keys and may lead to other security problems.
*/
- if (cmd->data[LOCK_CONFIG_IDX] || cmd->data[LOCK_VALUE_IDX]) {
- dev_err(&client->dev, "Configuration or Data and OTP zones are unlocked!\n");
+ if (cmd->data[ATMEL_I2C_LOCK_CONFIG_IDX] || cmd->data[ATMEL_I2C_LOCK_VALUE_IDX]) {
+ dev_err(&client->dev, "Config, Data and OTP zones are unlocked!\n");
ret = -ENOTSUPP;
}
@@ -575,6 +561,7 @@ static int device_sanity_check(struct i2c_client *client)
kfree(cmd);
return ret;
}
+EXPORT_SYMBOL(atmel_i2c_device_sanity_check);
void atmel_i2c_unregister_client(struct atmel_i2c_client_priv *i2c_priv)
{
@@ -633,7 +620,7 @@ int atmel_i2c_probe(struct i2c_client *client)
i2c_set_clientdata(client, i2c_priv);
- return device_sanity_check(client);
+ return 0;
}
EXPORT_SYMBOL(atmel_i2c_probe);
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index e30e0c417de2..2f76e107340e 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -82,18 +82,10 @@ struct atmel_i2c_of_match_data {
#define STATUS_NOERR 0x00
#define STATUS_WAKE_SUCCESSFUL 0x11
-/* Definitions for eeprom organization */
-#define CONFIGURATION_ZONE 0
-
/* Definitions for Indexes common to all commands */
-#define RSP_DATA_IDX 1 /* buffer index of data in response */
+#define ATMEL_I2C_RSP_DATA_IDX 1 /* buffer index of data in response */
#define DATA_SLOT_2 2 /* used for ECDH private key */
-/* Definitions for the device lock state */
-#define DEVICE_LOCK_ADDR 0x15
-#define LOCK_VALUE_IDX (RSP_DATA_IDX + 2)
-#define LOCK_CONFIG_IDX (RSP_DATA_IDX + 3)
-
/*
* Wake High delay to data communication (microseconds). SDA should be stable
* high for this entire duration.
@@ -195,8 +187,6 @@ void atmel_i2c_flush_queue(void);
int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd);
-void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd,
- const struct atmel_i2c_max_exec_timings *timings);
void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd,
const struct atmel_i2c_max_exec_timings *timings);
void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
@@ -207,6 +197,8 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
int atmel_i2c_register_rng(struct atmel_i2c_client_priv *i2c_priv,
struct device *dev);
+int atmel_i2c_device_sanity_check(struct i2c_client *client);
+
ssize_t atmel_i2c_eeprom_display(struct device *dev,
struct device_attribute *attr,
char *buf,
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index 341554b7b7a2..88726f6ef87c 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -64,6 +64,12 @@ static int atmel_sha204a_probe(struct i2c_client *client)
i2c_priv->data = data;
i2c_priv->caps = 0;
+ ret = atmel_i2c_device_sanity_check(client);
+ if (ret) {
+ dev_err(&client->dev, "failed to read EEPROM, is hardware attached?\n");
+ goto done;
+ }
+
/* add to client list */
spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
list_add_tail(&i2c_priv->i2c_client_list_node,
--
2.53.0
^ permalink raw reply related
* [PATCH 06/12] crypto: atmel - move EEPROM access support into common i2c core
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Move EEPROM read support from atmel-sha204a and atmel-ecc into the
shared atmel-i2c core and provide a generic interface for accessing
EEPROM zones on compatible Atmel devices.
Introduce enum atmel_i2c_eeprom_zones together with per-device EEPROM
zone sizing in struct atmel_i2c_of_match_data. Add common helpers for
EEPROM readout and sysfs formatting, and convert existing OTP sysfs
handling to use the shared infrastructure.
This removes duplicated EEPROM access logic from individual drivers and
extends support to ECC devices. The common implementation supports
CONFIG, OTP, and DATA zones using device-specific layout information
supplied via match data tables.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 36 ++++++++
drivers/crypto/atmel-i2c.c | 153 +++++++++++++++++++++++++--------
drivers/crypto/atmel-i2c.h | 30 +++----
drivers/crypto/atmel-sha204a.c | 65 ++++----------
4 files changed, 186 insertions(+), 98 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index 67fa5975fa7f..b5f2d44ec74c 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -23,6 +23,22 @@
#include <crypto/kpp.h>
#include "atmel-i2c.h"
+static ssize_t otp_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return atmel_i2c_eeprom_display(dev, attr, buf, ATMEL_EEPROM_OTP_ZONE);
+}
+static DEVICE_ATTR_RO(otp);
+
+static struct attribute *atmel_ecc508a_attrs[] = {
+ &dev_attr_otp.attr,
+ NULL
+};
+
+static const struct attribute_group atmel_ecc508a_groups = {
+ .name = "atecc508a",
+ .attrs = atmel_ecc508a_attrs,
+};
+
/**
* struct atmel_ecdh_ctx - transformation context
* @client : pointer to i2c client device
@@ -306,6 +322,18 @@ static int atmel_ecc_probe(struct i2c_client *client)
&atmel_i2c_mgmt.i2c_client_list);
spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+ /* EEPROM read out */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ ret = -ENODEV;
+ goto err_list_del;
+ }
+
+ ret = sysfs_create_group(&client->dev.kobj, &atmel_ecc508a_groups);
+ if (ret) {
+ dev_err(&client->dev, "failed to register sysfs entry\n");
+ goto err_list_del;
+ }
+
/* register rng */
ret = atmel_i2c_register_rng(i2c_priv, &client->dev);
if (ret) {
@@ -326,6 +354,7 @@ static int atmel_ecc_probe(struct i2c_client *client)
goto done;
err_list_del:
+ sysfs_remove_group(&client->dev.kobj, &atmel_ecc508a_groups);
spin_lock(&atmel_i2c_mgmt.i2c_list_lock);
list_del(&i2c_priv->i2c_client_list_node);
spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
@@ -361,6 +390,8 @@ static void atmel_ecc_remove(struct i2c_client *client)
kfree((void *)i2c_priv->hwrng.priv);
i2c_priv->hwrng.priv = 0;
}
+
+ sysfs_remove_group(&client->dev.kobj, &atmel_ecc508a_groups);
}
static const struct atmel_i2c_of_match_data atecc508a_match_data = {
@@ -371,6 +402,11 @@ static const struct atmel_i2c_of_match_data atecc508a_match_data = {
.max_exec_time_read = 1,
.max_exec_time_write = 42,
},
+ .eeprom_zone_size = {
+ [ATMEL_EEPROM_CONFIG_ZONE] = 128,
+ [ATMEL_EEPROM_OTP_ZONE] = 64,
+ [ATMEL_EEPROM_DATA_ZONE] = 1208
+ },
};
static const struct of_device_id atmel_ecc_dt_ids[] = {
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index d451017171d8..26863573a10f 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -21,6 +21,15 @@
#include <linux/workqueue.h>
#include "atmel-i2c.h"
+#define ATMEL_I2C_COMMAND 0x03 /* packet function */
+
+/* Command opcode */
+#define ATMEL_I2C_OPCODE_ECDH 0x43
+#define ATMEL_I2C_OPCODE_GENKEY 0x40
+#define ATMEL_I2C_OPCODE_READ 0x02
+#define ATMEL_I2C_OPCODE_RANDOM 0x1b
+#define ATMEL_I2C_OPCODE_WRITE 0x12
+
struct atmel_i2c_client_mgmt atmel_i2c_mgmt = {
.i2c_list_lock = __SPIN_LOCK_UNLOCKED(atmel_i2c_mgmt.i2c_list_lock),
.i2c_client_list = LIST_HEAD_INIT(atmel_i2c_mgmt.i2c_client_list),
@@ -96,56 +105,55 @@ struct i2c_client *atmel_i2c_client_alloc(enum atmel_i2c_capability cap)
}
EXPORT_SYMBOL(atmel_i2c_client_alloc);
-void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd,
- const struct atmel_i2c_max_exec_timings *timings)
+static int atmel_i2c_init_read_eeprom_cmd(struct atmel_i2c_cmd *cmd, u16 addr,
+ enum atmel_i2c_eeprom_zones zone,
+ const struct atmel_i2c_of_match_data *data)
{
- cmd->word_addr = COMMAND;
- cmd->opcode = OPCODE_READ;
- /*
- * Read the word from Configuration zone that contains the lock bytes
- * (UserExtra, Selector, LockValue, LockConfig).
- */
- cmd->param1 = CONFIGURATION_ZONE;
- cmd->param2 = cpu_to_le16(DEVICE_LOCK_ADDR);
- cmd->count = READ_COUNT;
+ const struct atmel_i2c_max_exec_timings *timings = &data->timings;
+ size_t zone_size = data->eeprom_zone_size[zone];
+
+ if (addr > zone_size)
+ return -EINVAL;
+
+ cmd->word_addr = ATMEL_I2C_COMMAND;
+ cmd->opcode = ATMEL_I2C_OPCODE_READ;
+ cmd->param1 = zone;
+ cmd->param2 = cpu_to_le16(addr);
+ cmd->count = ATMEL_I2C_READ_COUNT;
atmel_i2c_checksum(cmd);
cmd->msecs = timings->max_exec_time_read;
- cmd->rxsize = READ_RSP_SIZE;
+ cmd->rxsize = ATMEL_I2C_READ_RSP_SIZE;
+
+ return 0;
}
-EXPORT_SYMBOL(atmel_i2c_init_read_config_cmd);
-int atmel_i2c_init_read_otp_cmd(struct atmel_i2c_cmd *cmd, u16 addr,
- const struct atmel_i2c_max_exec_timings *timings)
+void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd,
+ const struct atmel_i2c_max_exec_timings *timings)
{
- if (addr >= OTP_ZONE_SIZE / 4)
- return -EINVAL;
-
- cmd->word_addr = COMMAND;
- cmd->opcode = OPCODE_READ;
+ cmd->word_addr = ATMEL_I2C_COMMAND;
+ cmd->opcode = ATMEL_I2C_OPCODE_READ;
/*
- * Read the word from OTP zone that may contain e.g. serial
- * numbers or similar if persistently pre-initialized and locked
+ * Read the word from Configuration zone that contains the lock bytes
+ * (UserExtra, Selector, LockValue, LockConfig).
*/
- cmd->param1 = OTP_ZONE;
- cmd->param2 = cpu_to_le16(addr);
- cmd->count = READ_COUNT;
+ cmd->param1 = CONFIGURATION_ZONE;
+ cmd->param2 = cpu_to_le16(DEVICE_LOCK_ADDR);
+ cmd->count = ATMEL_I2C_READ_COUNT;
atmel_i2c_checksum(cmd);
cmd->msecs = timings->max_exec_time_read;
- cmd->rxsize = READ_RSP_SIZE;
-
- return 0;
+ cmd->rxsize = ATMEL_I2C_READ_RSP_SIZE;
}
-EXPORT_SYMBOL(atmel_i2c_init_read_otp_cmd);
+EXPORT_SYMBOL(atmel_i2c_init_read_config_cmd);
void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd,
const struct atmel_i2c_max_exec_timings *timings)
{
- cmd->word_addr = COMMAND;
- cmd->opcode = OPCODE_RANDOM;
+ cmd->word_addr = ATMEL_I2C_COMMAND;
+ cmd->opcode = ATMEL_I2C_OPCODE_RANDOM;
cmd->param1 = 0;
cmd->param2 = 0;
cmd->count = RANDOM_COUNT;
@@ -160,9 +168,9 @@ EXPORT_SYMBOL(atmel_i2c_init_random_cmd);
void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
const struct atmel_i2c_max_exec_timings *timings)
{
- cmd->word_addr = COMMAND;
+ cmd->word_addr = ATMEL_I2C_COMMAND;
cmd->count = GENKEY_COUNT;
- cmd->opcode = OPCODE_GENKEY;
+ cmd->opcode = ATMEL_I2C_OPCODE_GENKEY;
cmd->param1 = GENKEY_MODE_PRIVATE;
/* a random private key will be generated and stored in slot keyID */
cmd->param2 = cpu_to_le16(keyid);
@@ -180,9 +188,9 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
{
size_t copied;
- cmd->word_addr = COMMAND;
+ cmd->word_addr = ATMEL_I2C_COMMAND;
cmd->count = ECDH_COUNT;
- cmd->opcode = OPCODE_ECDH;
+ cmd->opcode = ATMEL_I2C_OPCODE_ECDH;
cmd->param1 = ECDH_PREFIX_MODE;
/* private key slot */
cmd->param2 = cpu_to_le16(DATA_SLOT_2);
@@ -301,6 +309,81 @@ int atmel_i2c_register_rng(struct atmel_i2c_client_priv *i2c_priv,
}
EXPORT_SYMBOL(atmel_i2c_register_rng);
+static int atmel_i2c_eeprom_read(struct i2c_client *client, u16 addr,
+ enum atmel_i2c_eeprom_zones zone, u8 *buf)
+{
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+ struct atmel_i2c_cmd *cmd;
+ int ret = -1;
+
+ cmd = kmalloc_obj(*cmd);
+ if (!cmd)
+ return -ENOMEM;
+
+ ret = atmel_i2c_init_read_eeprom_cmd(cmd, addr, zone, data);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed, invalid eeprom address %04X\n",
+ addr);
+ goto err;
+ }
+
+ ret = atmel_i2c_send_receive(client, cmd);
+ if (ret)
+ goto err;
+
+ if (cmd->data[0] == 0xff) {
+ dev_err(&client->dev, "failed, device not ready\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ memcpy(buf, cmd->data + RSP_DATA_IDX, 4);
+
+err:
+ kfree(cmd);
+ return ret;
+}
+
+ssize_t atmel_i2c_eeprom_display(struct device *dev,
+ struct device_attribute *attr,
+ char *buf,
+ enum atmel_i2c_eeprom_zones zone)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ const struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+ const size_t *eeprom = data->eeprom_zone_size;
+ u16 block_addr;
+ u8 *eeprom_buf;
+ ssize_t len = 0;
+ int i, ret = 0;
+
+ eeprom_buf = kcalloc(eeprom[zone], sizeof(*eeprom_buf), GFP_KERNEL);
+ if (!eeprom_buf)
+ return -ENOMEM;
+
+ for (block_addr = 0; block_addr < eeprom[zone] / 4; block_addr++) {
+ ret = atmel_i2c_eeprom_read(client, block_addr, zone,
+ eeprom_buf + block_addr * 4);
+ if (ret < 0) {
+ dev_err(dev, "failed to read %s zone\n",
+ zone == ATMEL_EEPROM_CONFIG_ZONE ? "CONFIG"
+ : (zone == ATMEL_EEPROM_OTP_ZONE ? "OTP" : "DATA"));
+ goto err;
+ }
+ }
+
+ for (i = 0; i < eeprom[zone]; i++)
+ len += sysfs_emit_at(buf, len, "%02X", eeprom_buf[i]);
+ len += sysfs_emit_at(buf, len, "\n");
+ ret = len;
+err:
+ kfree(eeprom_buf);
+ return ret;
+}
+EXPORT_SYMBOL(atmel_i2c_eeprom_display);
+
/*
* After wake and after execution of a command, there will be error, status, or
* result bytes in the device's output register that can be retrieved by the
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index 5f6c9ff0cf64..e30e0c417de2 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -12,7 +12,6 @@
#define ATMEL_ECC_PRIORITY 300
-#define COMMAND 0x03 /* packet function */
#define SLEEP_TOKEN 0x01
#define WAKE_TOKEN_MAX_SIZE 8
@@ -30,7 +29,7 @@
#define ECDH_RSP_SIZE (32 + CMD_OVERHEAD_SIZE)
#define GENKEY_RSP_SIZE (ATMEL_ECC_PUBKEY_SIZE + \
CMD_OVERHEAD_SIZE)
-#define READ_RSP_SIZE (4 + CMD_OVERHEAD_SIZE)
+#define ATMEL_I2C_READ_RSP_SIZE (4 + CMD_OVERHEAD_SIZE)
#define RANDOM_RSP_SIZE (32 + CMD_OVERHEAD_SIZE)
#define MAX_RSP_SIZE GENKEY_RSP_SIZE
@@ -57,6 +56,13 @@ struct atmel_i2c_cmd {
u16 rxsize;
} __packed;
+/* Definitions for eeprom organization */
+enum atmel_i2c_eeprom_zones {
+ ATMEL_EEPROM_CONFIG_ZONE = 0,
+ ATMEL_EEPROM_OTP_ZONE = 1,
+ ATMEL_EEPROM_DATA_ZONE = 2,
+};
+
struct atmel_i2c_max_exec_timings {
unsigned int max_exec_time_genkey;
unsigned int max_exec_time_ecdh;
@@ -68,6 +74,7 @@ struct atmel_i2c_max_exec_timings {
struct atmel_i2c_of_match_data {
const unsigned short needs_legacy_hwrng;
struct atmel_i2c_max_exec_timings timings;
+ size_t eeprom_zone_size[3]; /* all atmel devices have three zones */
};
/* Status/Error codes */
@@ -77,10 +84,6 @@ struct atmel_i2c_of_match_data {
/* Definitions for eeprom organization */
#define CONFIGURATION_ZONE 0
-#define OTP_ZONE 1
-
-/* Definitions for eeprom zone sizes */
-#define OTP_ZONE_SIZE 64
/* Definitions for Indexes common to all commands */
#define RSP_DATA_IDX 1 /* buffer index of data in response */
@@ -101,14 +104,8 @@ struct atmel_i2c_of_match_data {
/* Wake Low duration */
#define TWLO_USEC 60
-/* Command opcode */
-#define OPCODE_ECDH 0x43
-#define OPCODE_GENKEY 0x40
-#define OPCODE_READ 0x02
-#define OPCODE_RANDOM 0x1b
-
/* Definitions for the READ Command */
-#define READ_COUNT 7
+#define ATMEL_I2C_READ_COUNT 7
/* Definitions for the RANDOM Command */
#define RANDOM_COUNT 7
@@ -200,8 +197,6 @@ int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd)
void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd,
const struct atmel_i2c_max_exec_timings *timings);
-int atmel_i2c_init_read_otp_cmd(struct atmel_i2c_cmd *cmd, u16 addr,
- const struct atmel_i2c_max_exec_timings *timings);
void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd,
const struct atmel_i2c_max_exec_timings *timings);
void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
@@ -212,6 +207,11 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
int atmel_i2c_register_rng(struct atmel_i2c_client_priv *i2c_priv,
struct device *dev);
+ssize_t atmel_i2c_eeprom_display(struct device *dev,
+ struct device_attribute *attr,
+ char *buf,
+ enum atmel_i2c_eeprom_zones zone);
+
struct i2c_client *atmel_i2c_client_alloc(enum atmel_i2c_capability cap);
void atmel_i2c_unregister_client(struct atmel_i2c_client_priv *i2c_priv);
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index ae24d8fbabf9..4f10e826e675 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -19,57 +19,10 @@
#include <linux/workqueue.h>
#include "atmel-i2c.h"
-static int atmel_sha204a_otp_read(struct i2c_client *client, u16 addr, u8 *otp)
-{
- struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
- const struct atmel_i2c_of_match_data *data = i2c_priv->data;
- struct atmel_i2c_cmd cmd;
- int ret;
-
- ret = atmel_i2c_init_read_otp_cmd(&cmd, addr, &data->timings);
- if (ret < 0) {
- dev_err(&client->dev, "failed, invalid otp address %04X\n",
- addr);
- return ret;
- }
-
- ret = atmel_i2c_send_receive(client, &cmd);
- if (ret < 0) {
- dev_err(&client->dev, "failed to read otp at %04X\n", addr);
- return ret;
- }
-
- if (cmd.data[0] == 0xff) {
- dev_err(&client->dev, "failed, device not ready\n");
- return -EIO;
- }
-
- memcpy(otp, cmd.data+1, 4);
-
- return ret;
-}
-
static ssize_t otp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- u16 addr;
- u8 otp[OTP_ZONE_SIZE];
- struct i2c_client *client = to_i2c_client(dev);
- ssize_t len = 0;
- int i, ret;
-
- for (addr = 0; addr < OTP_ZONE_SIZE / 4; addr++) {
- ret = atmel_sha204a_otp_read(client, addr, otp + addr * 4);
- if (ret < 0) {
- dev_err(dev, "failed to read otp zone\n");
- return ret;
- }
- }
-
- for (i = 0; i < OTP_ZONE_SIZE; i++)
- len += sysfs_emit_at(buf, len, "%02X", otp[i]);
- len += sysfs_emit_at(buf, len, "\n");
- return len;
+ return atmel_i2c_eeprom_display(dev, attr, buf, ATMEL_EEPROM_OTP_ZONE);
}
static DEVICE_ATTR_RO(otp);
@@ -110,6 +63,12 @@ static int atmel_sha204a_probe(struct i2c_client *client)
&atmel_i2c_mgmt.i2c_client_list);
spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+ /* EEPROM read out */
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ ret = -ENODEV;
+ goto err_list_del;
+ }
+
ret = sysfs_create_group(&client->dev.kobj, &atmel_sha204a_groups);
if (ret) {
dev_err(&client->dev, "failed to register sysfs entry\n");
@@ -157,6 +116,11 @@ static const struct atmel_i2c_of_match_data atsha204_match_data = {
.max_exec_time_read = 4,
.max_exec_time_write = 42,
},
+ .eeprom_zone_size = {
+ [ATMEL_EEPROM_CONFIG_ZONE] = 88,
+ [ATMEL_EEPROM_OTP_ZONE] = 64,
+ [ATMEL_EEPROM_DATA_ZONE] = 512
+ },
/*
* According to review by Bill Cox [1], the ATSHA204 has very low entropy.
* [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
@@ -171,6 +135,11 @@ static const struct atmel_i2c_of_match_data atsha204a_match_data = {
.max_exec_time_read = 4,
.max_exec_time_write = 42,
},
+ .eeprom_zone_size = {
+ [ATMEL_EEPROM_CONFIG_ZONE] = 88,
+ [ATMEL_EEPROM_OTP_ZONE] = 64,
+ [ATMEL_EEPROM_DATA_ZONE] = 512
+ },
};
static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = {
--
2.53.0
^ permalink raw reply related
* [PATCH 05/12] crypto: atmel - move RNG support into common i2c core
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Move the hardware RNG implementation from atmel-sha204a into the
shared atmel-i2c core.
The ATSHA204(A) and ATECC devices provide compatible RANDOM commands
through the common Atmel I2C interface. Consolidate the RNG handling in
the core driver and provide a shared atmel_i2c_register_rng() helper for
registering the hwrng device.
This removes duplicated RNG code from atmel-sha204a and enables RNG
support for other compatible Atmel devices, including the ECC family.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 12 ++++
drivers/crypto/atmel-i2c.c | 93 ++++++++++++++++++++++++++
drivers/crypto/atmel-i2c.h | 4 +-
drivers/crypto/atmel-sha204a.c | 115 +++++----------------------------
4 files changed, 123 insertions(+), 101 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index 7793f7b4e97e..67fa5975fa7f 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -306,6 +306,13 @@ static int atmel_ecc_probe(struct i2c_client *client)
&atmel_i2c_mgmt.i2c_client_list);
spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
+ /* register rng */
+ ret = atmel_i2c_register_rng(i2c_priv, &client->dev);
+ if (ret) {
+ dev_err(&client->dev, "failed to register hw_random\n");
+ goto err_list_del;
+ }
+
/* register algorithms */
ret = crypto_register_kpp(&atmel_ecdh_nist_p256);
if (ret) {
@@ -349,6 +356,11 @@ static void atmel_ecc_remove(struct i2c_client *client)
atmel_i2c_flush_queue();
crypto_unregister_kpp(&atmel_ecdh_nist_p256);
+
+ if (i2c_priv->hwrng.priv) {
+ kfree((void *)i2c_priv->hwrng.priv);
+ i2c_priv->hwrng.priv = 0;
+ }
}
static const struct atmel_i2c_of_match_data atecc508a_match_data = {
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index 7fa7cf9ab3c1..d451017171d8 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -208,6 +208,99 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
}
EXPORT_SYMBOL(atmel_i2c_init_ecdh_cmd);
+static void atmel_i2c_rng_done(struct atmel_i2c_work_data *work_data,
+ void *areq, int status)
+{
+ struct atmel_i2c_client_priv *i2c_priv = work_data->ctx;
+ struct hwrng *rng = areq;
+
+ if (status)
+ dev_warn_ratelimited(&i2c_priv->client->dev,
+ "i2c transaction failed (%d)\n",
+ status);
+
+ rng->priv = (unsigned long)work_data;
+ atomic_dec(&i2c_priv->tfm_count);
+}
+
+static int atmel_i2c_rng_read_nonblocking(struct hwrng *rng, void *buf,
+ size_t max)
+{
+ struct atmel_i2c_client_priv *i2c_priv = container_of(rng,
+ struct atmel_i2c_client_priv,
+ hwrng);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+ struct atmel_i2c_work_data *work_data;
+
+ /* keep maximum 1 asynchronous read in flight at any time */
+ if (!atomic_add_unless(&i2c_priv->tfm_count, 1, 1))
+ return 0;
+
+ if (rng->priv) {
+ work_data = (struct atmel_i2c_work_data *)rng->priv;
+ max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
+ memcpy(buf, &work_data->cmd.data[RSP_DATA_IDX], max);
+ rng->priv = 0;
+ } else {
+ work_data = kmalloc_obj(*work_data, GFP_ATOMIC);
+ if (!work_data) {
+ atomic_dec(&i2c_priv->tfm_count);
+ return -ENOMEM;
+ }
+ work_data->ctx = i2c_priv;
+ work_data->client = i2c_priv->client;
+
+ max = 0;
+ }
+
+ atmel_i2c_init_random_cmd(&work_data->cmd, &data->timings);
+ atmel_i2c_enqueue(work_data, atmel_i2c_rng_done, rng);
+
+ return max;
+}
+
+static int atmel_i2c_rng_read(struct hwrng *rng, void *buf, size_t max,
+ bool wait)
+{
+ struct atmel_i2c_client_priv *i2c_priv = container_of(rng,
+ struct atmel_i2c_client_priv,
+ hwrng);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+ struct atmel_i2c_cmd cmd;
+ int ret;
+
+ if (!wait)
+ return atmel_i2c_rng_read_nonblocking(rng, buf, max);
+
+ atmel_i2c_init_random_cmd(&cmd, &data->timings);
+
+ ret = atmel_i2c_send_receive(i2c_priv->client, &cmd);
+ if (ret)
+ return ret;
+
+ max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
+ memcpy(buf, &cmd.data[RSP_DATA_IDX], max);
+
+ return max;
+}
+
+int atmel_i2c_register_rng(struct atmel_i2c_client_priv *i2c_priv,
+ struct device *dev)
+{
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+
+ memset(&i2c_priv->hwrng, 0, sizeof(i2c_priv->hwrng));
+
+ i2c_priv->hwrng.name = dev_name(dev);
+ i2c_priv->hwrng.read = atmel_i2c_rng_read;
+
+ if (data->needs_legacy_hwrng)
+ i2c_priv->hwrng.quality = data->needs_legacy_hwrng;
+
+ return devm_hwrng_register(dev, &i2c_priv->hwrng);
+}
+EXPORT_SYMBOL(atmel_i2c_register_rng);
+
/*
* After wake and after execution of a command, there will be error, status, or
* result bytes in the device's output register that can be retrieved by the
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index 5224a62c16c9..5f6c9ff0cf64 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -66,7 +66,7 @@ struct atmel_i2c_max_exec_timings {
};
struct atmel_i2c_of_match_data {
- const unsigned short *legacy_hwrng;
+ const unsigned short needs_legacy_hwrng;
struct atmel_i2c_max_exec_timings timings;
};
@@ -209,6 +209,8 @@ void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
struct scatterlist *pubkey,
const struct atmel_i2c_max_exec_timings *timings);
+int atmel_i2c_register_rng(struct atmel_i2c_client_priv *i2c_priv,
+ struct device *dev);
struct i2c_client *atmel_i2c_client_alloc(enum atmel_i2c_capability cap);
void atmel_i2c_unregister_client(struct atmel_i2c_client_priv *i2c_priv);
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index febf9891b167..ae24d8fbabf9 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -19,88 +19,6 @@
#include <linux/workqueue.h>
#include "atmel-i2c.h"
-/*
- * According to review by Bill Cox [1], the ATSHA204 has very low entropy.
- * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
- */
-static const unsigned short atsha204_quality = 1;
-
-static void atmel_sha204a_rng_done(struct atmel_i2c_work_data *work_data,
- void *areq, int status)
-{
- struct atmel_i2c_client_priv *i2c_priv = work_data->ctx;
- struct hwrng *rng = areq;
-
- if (status)
- dev_warn_ratelimited(&i2c_priv->client->dev,
- "i2c transaction failed (%d)\n",
- status);
-
- rng->priv = (unsigned long)work_data;
- atomic_dec(&i2c_priv->tfm_count);
-}
-
-static int atmel_sha204a_rng_read_nonblocking(struct hwrng *rng, void *buf,
- size_t max)
-{
- struct atmel_i2c_client_priv *i2c_priv = container_of(rng,
- struct atmel_i2c_client_priv,
- hwrng);
- const struct atmel_i2c_of_match_data *data = i2c_priv->data;
- struct atmel_i2c_work_data *work_data;
-
- /* keep maximum 1 asynchronous read in flight at any time */
- if (!atomic_add_unless(&i2c_priv->tfm_count, 1, 1))
- return 0;
-
- if (rng->priv) {
- work_data = (struct atmel_i2c_work_data *)rng->priv;
- max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
- memcpy(buf, &work_data->cmd.data[RSP_DATA_IDX], max);
- rng->priv = 0;
- } else {
- work_data = kmalloc_obj(*work_data, GFP_ATOMIC);
- if (!work_data) {
- atomic_dec(&i2c_priv->tfm_count);
- return -ENOMEM;
- }
- work_data->ctx = i2c_priv;
- work_data->client = i2c_priv->client;
-
- max = 0;
- }
-
- atmel_i2c_init_random_cmd(&work_data->cmd, &data->timings);
- atmel_i2c_enqueue(work_data, atmel_sha204a_rng_done, rng);
-
- return max;
-}
-
-static int atmel_sha204a_rng_read(struct hwrng *rng, void *buf, size_t max,
- bool wait)
-{
- struct atmel_i2c_client_priv *i2c_priv = container_of(rng,
- struct atmel_i2c_client_priv,
- hwrng);
- const struct atmel_i2c_of_match_data *data = i2c_priv->data;
- struct atmel_i2c_cmd cmd;
- int ret;
-
- if (!wait)
- return atmel_sha204a_rng_read_nonblocking(rng, buf, max);
-
- atmel_i2c_init_random_cmd(&cmd, &data->timings);
-
- ret = atmel_i2c_send_receive(i2c_priv->client, &cmd);
- if (ret)
- return ret;
-
- max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
- memcpy(buf, &cmd.data[RSP_DATA_IDX], max);
-
- return max;
-}
-
static int atmel_sha204a_otp_read(struct i2c_client *client, u16 addr, u8 *otp)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
@@ -169,7 +87,6 @@ static int atmel_sha204a_probe(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv;
const struct atmel_i2c_of_match_data *data;
- const unsigned short *quality;
int ret;
ret = atmel_i2c_probe(client);
@@ -193,25 +110,16 @@ static int atmel_sha204a_probe(struct i2c_client *client)
&atmel_i2c_mgmt.i2c_client_list);
spin_unlock(&atmel_i2c_mgmt.i2c_list_lock);
- /* register rng */
- memset(&i2c_priv->hwrng, 0, sizeof(i2c_priv->hwrng));
-
- i2c_priv->hwrng.name = dev_name(&client->dev);
- i2c_priv->hwrng.read = atmel_sha204a_rng_read;
-
- quality = i2c_priv->data->legacy_hwrng;
- if (quality)
- i2c_priv->hwrng.quality = *quality;
-
- ret = devm_hwrng_register(&client->dev, &i2c_priv->hwrng);
+ ret = sysfs_create_group(&client->dev.kobj, &atmel_sha204a_groups);
if (ret) {
- dev_warn(&client->dev, "failed to register RNG (%d)\n", ret);
+ dev_err(&client->dev, "failed to register sysfs entry\n");
goto err_list_del;
}
- ret = sysfs_create_group(&client->dev.kobj, &atmel_sha204a_groups);
+ /* register rng */
+ ret = atmel_i2c_register_rng(i2c_priv, &client->dev);
if (ret) {
- dev_err(&client->dev, "failed to register sysfs entry\n");
+ dev_err(&client->dev, "failed to register hw_random\n");
goto err_list_del;
}
@@ -234,9 +142,12 @@ static void atmel_sha204a_remove(struct i2c_client *client)
devm_hwrng_unregister(&client->dev, &i2c_priv->hwrng);
atmel_i2c_flush_queue();
- sysfs_remove_group(&client->dev.kobj, &atmel_sha204a_groups);
+ if (i2c_priv->hwrng.priv) {
+ kfree((void *)i2c_priv->hwrng.priv);
+ i2c_priv->hwrng.priv = 0;
+ }
- kfree((void *)i2c_priv->hwrng.priv);
+ sysfs_remove_group(&client->dev.kobj, &atmel_sha204a_groups);
}
static const struct atmel_i2c_of_match_data atsha204_match_data = {
@@ -246,7 +157,11 @@ static const struct atmel_i2c_of_match_data atsha204_match_data = {
.max_exec_time_read = 4,
.max_exec_time_write = 42,
},
- .legacy_hwrng = &atsha204_quality,
+ /*
+ * According to review by Bill Cox [1], the ATSHA204 has very low entropy.
+ * [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
+ */
+ .needs_legacy_hwrng = 1,
};
static const struct atmel_i2c_of_match_data atsha204a_match_data = {
--
2.53.0
^ permalink raw reply related
* [PATCH 10/12] crypto: atmel - update workqueue flags and add flush on exit
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Update workqueue initialization to use WQ_MEM_RECLAIM instead of
WQ_PERCPU. WQ_MEM_RECLAIM already provides per-CPU execution
semantics via a bound workqueue while also ensuring forward progress
via a rescue thread.
Add a flush_workqueue() call during module exit to ensure all queued
work is completed before destroying the workqueue.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-i2c.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index 50b6bce478d2..0ec2d768a763 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -626,12 +626,13 @@ EXPORT_SYMBOL(atmel_i2c_probe);
static int __init atmel_i2c_init(void)
{
- atmel_wq = alloc_workqueue("atmel_wq", WQ_PERCPU, 0);
+ atmel_wq = alloc_workqueue("atmel_wq", WQ_MEM_RECLAIM, 0);
return atmel_wq ? 0 : -ENOMEM;
}
static void __exit atmel_i2c_exit(void)
{
+ flush_workqueue(atmel_wq);
destroy_workqueue(atmel_wq);
}
--
2.53.0
^ permalink raw reply related
* [PATCH 11/12] crypto: atmel - refactor and localize driver constants
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
After refactoring the client drivers to use the shared atmel-i2c core,
many constants and definitions no longer need global visibility.
Move command definitions, timing constants, status codes and related
helpers from the public header into the local compile unit of the core
driver where possible.
As part of this cleanup, rename macros and constants to use consistent
ATMEL_I2C_* naming and align them with common kernel driver conventions.
Also replace remaining hardcoded values with named constants throughout
the driver.
This is a preparatory cleanup and does not change functionality.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 4 +-
drivers/crypto/atmel-i2c.c | 115 +++++++++++++++++++++++++------------
drivers/crypto/atmel-i2c.h | 76 +++++++++---------------
3 files changed, 108 insertions(+), 87 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index 9ad6d42b6eef..ed8c0ce5562b 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -137,7 +137,7 @@ static int atmel_ecdh_set_secret(struct crypto_kpp *tfm, const void *buf,
ctx->do_fallback = false;
- atmel_i2c_init_genkey_cmd(cmd, DATA_SLOT_2, &data->timings);
+ atmel_i2c_init_genkey_cmd(cmd, ATMEL_I2C_ECDH_SLOT_DEFAULT, &data->timings);
ret = atmel_i2c_send_receive(ctx->client, cmd);
if (ret)
@@ -296,7 +296,7 @@ static struct kpp_alg atmel_ecdh_nist_p256 = {
.cra_flags = CRYPTO_ALG_NEED_FALLBACK,
.cra_name = "ecdh-nist-p256",
.cra_driver_name = "atmel-ecdh",
- .cra_priority = ATMEL_ECC_PRIORITY,
+ .cra_priority = ATMEL_I2C_PRIORITY,
.cra_module = THIS_MODULE,
.cra_ctxsize = sizeof(struct atmel_ecdh_ctx),
},
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index 0ec2d768a763..53aba2f4bedb 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -21,19 +21,62 @@
#include <linux/workqueue.h>
#include "atmel-i2c.h"
-#define ATMEL_I2C_COMMAND 0x03 /* packet function */
+#define ATMEL_I2C_COMMAND 0x03 /* packet function */
+#define ATMEL_I2C_SLEEP_TOKEN 0x01
/* Definitions for the device lock state */
-#define ATMEL_I2C_DEVICE_LOCK_ADDR 0x15
-#define ATMEL_I2C_LOCK_VALUE_IDX (ATMEL_I2C_RSP_DATA_IDX + 2)
-#define ATMEL_I2C_LOCK_CONFIG_IDX (ATMEL_I2C_RSP_DATA_IDX + 3)
+#define ATMEL_I2C_DEVICE_LOCK_ADDR 0x15
+#define ATMEL_I2C_LOCK_VALUE_IDX (ATMEL_I2C_RSP_DATA_IDX + 2)
+#define ATMEL_I2C_LOCK_CONFIG_IDX (ATMEL_I2C_RSP_DATA_IDX + 3)
+
+/* Definitions for the READ Command */
+#define ATMEL_I2C_READ_COUNT ATMEL_I2C_COUNT_OVERHEAD_SIZE
+#define ATMEL_I2C_READ_RSP_SIZE (4 + ATMEL_I2C_RSP_OVERHEAD_SIZE)
+
+/* Definitions for the RANDOM Command */
+#define ATMEL_I2C_RANDOM_COUNT ATMEL_I2C_COUNT_OVERHEAD_SIZE
+#define ATMEL_I2C_RNG_BLOCK_SIZE 32
+#define ATMEL_I2C_RANDOM_RSP_SIZE (ATMEL_I2C_RNG_BLOCK_SIZE + \
+ ATMEL_I2C_RSP_OVERHEAD_SIZE)
+#define ATMEL_I2C_RANDOM_COUNT ATMEL_I2C_COUNT_OVERHEAD_SIZE
+
+/* Definitions for the GenKey Command */
+#define ATMEL_I2C_GENKEY_COUNT ATMEL_I2C_COUNT_OVERHEAD_SIZE
+#define ATMEL_I2C_GENKEY_MODE_PRIVATE 0x04
+
+/* Definitions for the ECDH Command */
+#define ATMEL_I2C_ECDH_COUNT 71
+#define ATMEL_I2C_ECDH_RSP_SIZE (32 + ATMEL_I2C_RSP_OVERHEAD_SIZE)
+#define ATMEL_I2C_ECDH_PREFIX_MODE 0x00
/* Command opcode */
-#define ATMEL_I2C_OPCODE_ECDH 0x43
-#define ATMEL_I2C_OPCODE_GENKEY 0x40
-#define ATMEL_I2C_OPCODE_READ 0x02
-#define ATMEL_I2C_OPCODE_RANDOM 0x1b
-#define ATMEL_I2C_OPCODE_WRITE 0x12
+#define ATMEL_I2C_OPCODE_ECDH 0x43
+#define ATMEL_I2C_OPCODE_GENKEY 0x40
+#define ATMEL_I2C_OPCODE_READ 0x02
+#define ATMEL_I2C_OPCODE_RANDOM 0x1b
+#define ATMEL_I2C_OPCODE_WRITE 0x12
+
+/*
+ * Wake High delay to data communication (microseconds). SDA should be stable
+ * high for this entire duration.
+ */
+#define ATMEL_I2C_TWHI_MIN 1500
+#define ATMEL_I2C_TWHI_MAX 1550
+
+/* Wake Low duration */
+#define ATMEL_I2C_TWLO_USEC 60
+
+/* Status/Error codes */
+enum atmel_i2c_error_codes {
+ ATMEL_STATUS_OK_NOERR = 0x00, /* success */
+ ATMEL_STATUS_CHECKMAC_OR_VERIFY_MISCOMPARE = 0x01,
+ ATMEL_STATUS_PARSE_ERROR = 0x03,
+ ATMEL_STATUS_ECC_FAULT = 0x05,
+ ATMEL_STATUS_EXECUTION_FAULT = 0x0F,
+ ATMEL_STATUS_OK_WAKE_SUCCESSFULL = 0x11, /* success */
+ ATMEL_STATUS_WATCHDOG_EXPIRE = 0xEE,
+ ATMEL_STATUS_CRC_ERROR = 0xFF,
+};
struct atmel_i2c_client_mgmt atmel_i2c_mgmt = {
.i2c_list_lock = __SPIN_LOCK_UNLOCKED(atmel_i2c_mgmt.i2c_list_lock),
@@ -45,12 +88,12 @@ static const struct {
u8 value;
const char *error_text;
} error_list[] = {
- { 0x01, "CheckMac or Verify miscompare" },
- { 0x03, "Parse Error" },
- { 0x05, "ECC Fault" },
- { 0x0F, "Execution Error" },
- { 0xEE, "Watchdog about to expire" },
- { 0xFF, "CRC or other communication error" },
+ { ATMEL_STATUS_CHECKMAC_OR_VERIFY_MISCOMPARE, "CheckMac or Verify miscompare" },
+ { ATMEL_STATUS_PARSE_ERROR, "Parse Error" },
+ { ATMEL_STATUS_ECC_FAULT, "ECC Fault" },
+ { ATMEL_STATUS_EXECUTION_FAULT, "Execution Error" },
+ { ATMEL_STATUS_WATCHDOG_EXPIRE, "Watchdog about to expire" },
+ { ATMEL_STATUS_CRC_ERROR, "CRC or other communication error" },
};
/**
@@ -65,7 +108,7 @@ static const struct {
static void atmel_i2c_checksum(struct atmel_i2c_cmd *cmd)
{
u8 *data = &cmd->count;
- size_t len = cmd->count - CRC_SIZE;
+ size_t len = cmd->count - ATMEL_I2C_CRC_SIZE;
__le16 *__crc16 = (__le16 *)(data + len);
*__crc16 = cpu_to_le16(bitrev16(crc16(0, data, len)));
@@ -141,12 +184,12 @@ void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd,
cmd->opcode = ATMEL_I2C_OPCODE_RANDOM;
cmd->param1 = 0;
cmd->param2 = 0;
- cmd->count = RANDOM_COUNT;
+ cmd->count = ATMEL_I2C_RANDOM_COUNT;
atmel_i2c_checksum(cmd);
cmd->msecs = timings->max_exec_time_random;
- cmd->rxsize = RANDOM_RSP_SIZE;
+ cmd->rxsize = ATMEL_I2C_RANDOM_RSP_SIZE;
}
EXPORT_SYMBOL(atmel_i2c_init_random_cmd);
@@ -154,16 +197,16 @@ void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
const struct atmel_i2c_max_exec_timings *timings)
{
cmd->word_addr = ATMEL_I2C_COMMAND;
- cmd->count = GENKEY_COUNT;
+ cmd->count = ATMEL_I2C_GENKEY_COUNT;
cmd->opcode = ATMEL_I2C_OPCODE_GENKEY;
- cmd->param1 = GENKEY_MODE_PRIVATE;
+ cmd->param1 = ATMEL_I2C_GENKEY_MODE_PRIVATE;
/* a random private key will be generated and stored in slot keyID */
cmd->param2 = cpu_to_le16(keyid);
atmel_i2c_checksum(cmd);
cmd->msecs = timings->max_exec_time_genkey;
- cmd->rxsize = GENKEY_RSP_SIZE;
+ cmd->rxsize = ATMEL_I2C_GENKEY_RSP_SIZE;
}
EXPORT_SYMBOL(atmel_i2c_init_genkey_cmd);
@@ -174,11 +217,11 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
size_t copied;
cmd->word_addr = ATMEL_I2C_COMMAND;
- cmd->count = ECDH_COUNT;
+ cmd->count = ATMEL_I2C_ECDH_COUNT;
cmd->opcode = ATMEL_I2C_OPCODE_ECDH;
- cmd->param1 = ECDH_PREFIX_MODE;
+ cmd->param1 = ATMEL_I2C_ECDH_PREFIX_MODE;
/* private key slot */
- cmd->param2 = cpu_to_le16(DATA_SLOT_2);
+ cmd->param2 = cpu_to_le16(ATMEL_I2C_ECDH_SLOT_DEFAULT);
/*
* The device only supports NIST P256 ECC keys. The public key size will
@@ -195,7 +238,7 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
atmel_i2c_checksum(cmd);
cmd->msecs = timings->max_exec_time_ecdh;
- cmd->rxsize = ECDH_RSP_SIZE;
+ cmd->rxsize = ATMEL_I2C_ECDH_RSP_SIZE;
return 0;
}
@@ -231,7 +274,7 @@ static int atmel_i2c_rng_read_nonblocking(struct hwrng *rng, void *buf,
if (rng->priv) {
work_data = (struct atmel_i2c_work_data *)rng->priv;
- max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
+ max = min(ATMEL_I2C_RANDOM_RSP_SIZE - ATMEL_I2C_RSP_OVERHEAD_SIZE, max);
memcpy(buf, &work_data->cmd.data[ATMEL_I2C_RSP_DATA_IDX], max);
rng->priv = 0;
} else {
@@ -271,7 +314,7 @@ static int atmel_i2c_rng_read(struct hwrng *rng, void *buf, size_t max,
if (ret)
return ret;
- max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
+ max = min(ATMEL_I2C_RANDOM_RSP_SIZE - ATMEL_I2C_RSP_OVERHEAD_SIZE, max);
memcpy(buf, &cmd.data[ATMEL_I2C_RSP_DATA_IDX], max);
return max;
@@ -323,7 +366,7 @@ static int atmel_i2c_eeprom_read(struct i2c_client *client, u16 addr,
goto err;
}
- memcpy(buf, cmd->data + ATMEL_I2C_RSP_DATA_IDX, 4);
+ memcpy(buf, cmd->data + ATMEL_I2C_RSP_DATA_IDX, ATMEL_I2C_STATUS_RSP_SIZE);
err:
kfree(cmd);
@@ -381,10 +424,10 @@ static int atmel_i2c_status(struct device *dev, u8 *status)
int i;
u8 err_id = status[1];
- if (*status != STATUS_SIZE)
+ if (*status != ATMEL_I2C_STATUS_RSP_SIZE)
return 0;
- if (err_id == STATUS_WAKE_SUCCESSFUL || err_id == STATUS_NOERR)
+ if (err_id == ATMEL_STATUS_OK_WAKE_SUCCESSFULL || err_id == ATMEL_STATUS_OK_NOERR)
return 0;
for (i = 0; i < err_list_len; i++)
@@ -403,7 +446,7 @@ static int atmel_i2c_status(struct device *dev, u8 *status)
static int atmel_i2c_wakeup(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
- u8 status[STATUS_RSP_SIZE];
+ u8 status[ATMEL_I2C_STATUS_RSP_SIZE];
int ret;
/*
@@ -418,9 +461,9 @@ static int atmel_i2c_wakeup(struct i2c_client *client)
* Wait to wake the device. Typical execution times for ecdh and genkey
* are around tens of milliseconds. Delta is chosen to 50 microseconds.
*/
- usleep_range(TWHI_MIN, TWHI_MAX);
+ usleep_range(ATMEL_I2C_TWHI_MIN, ATMEL_I2C_TWHI_MAX);
- ret = i2c_master_recv(client, status, STATUS_SIZE);
+ ret = i2c_master_recv(client, status, ATMEL_I2C_STATUS_RSP_SIZE);
if (ret < 0)
return ret;
@@ -429,7 +472,7 @@ static int atmel_i2c_wakeup(struct i2c_client *client)
static int atmel_i2c_sleep(struct i2c_client *client)
{
- u8 sleep = SLEEP_TOKEN;
+ u8 sleep = ATMEL_I2C_SLEEP_TOKEN;
return i2c_master_send(client, &sleep, 1);
}
@@ -461,7 +504,7 @@ int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd)
goto err;
/* send the command */
- ret = i2c_master_send(client, (u8 *)cmd, cmd->count + WORD_ADDR_SIZE);
+ ret = i2c_master_send(client, (u8 *)cmd, cmd->count + ATMEL_I2C_ADDR_SIZE);
if (ret < 0)
goto err;
@@ -521,7 +564,7 @@ EXPORT_SYMBOL(atmel_i2c_flush_queue);
static inline size_t atmel_i2c_wake_token_sz(u32 bus_clk_rate)
{
- u32 no_of_bits = DIV_ROUND_UP(TWLO_USEC * bus_clk_rate, USEC_PER_SEC);
+ u32 no_of_bits = DIV_ROUND_UP(ATMEL_I2C_TWLO_USEC * bus_clk_rate, USEC_PER_SEC);
/* return the size of the wake_token in bytes */
return DIV_ROUND_UP(no_of_bits, 8);
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index 2f76e107340e..20afe2da4f8d 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -10,28 +10,39 @@
#include <linux/hw_random.h>
#include <linux/types.h>
-#define ATMEL_ECC_PRIORITY 300
+#define ATMEL_I2C_PRIORITY 300
-#define SLEEP_TOKEN 0x01
-#define WAKE_TOKEN_MAX_SIZE 8
+#define ATMEL_I2C_WAKE_TOKEN_MAX_SIZE 8
/* Definitions of Data and Command sizes */
-#define WORD_ADDR_SIZE 1
-#define COUNT_SIZE 1
-#define CRC_SIZE 2
-#define CMD_OVERHEAD_SIZE (COUNT_SIZE + CRC_SIZE)
+#define ATMEL_I2C_ADDR_SIZE 1
+#define ATMEL_I2C_OPCODE_SIZE 1
+#define ATMEL_I2C_COUNT_SIZE 1
+#define ATMEL_I2C_PARAM1_SIZE 1
+#define ATMEL_I2C_PARAM2_SIZE 2
+#define ATMEL_I2C_CRC_SIZE 2
+
+#define ATMEL_I2C_RSP_OVERHEAD_SIZE (ATMEL_I2C_COUNT_SIZE + \
+ ATMEL_I2C_CRC_SIZE)
+#define ATMEL_I2C_COUNT_OVERHEAD_SIZE (ATMEL_I2C_OPCODE_SIZE + \
+ ATMEL_I2C_COUNT_SIZE + \
+ ATMEL_I2C_PARAM1_SIZE + \
+ ATMEL_I2C_PARAM2_SIZE + \
+ ATMEL_I2C_CRC_SIZE)
+
+/* Definitions for the status Command */
+#define ATMEL_I2C_STATUS_RSP_SIZE 4
/* size in bytes of the n prime */
#define ATMEL_ECC_NIST_P256_N_SIZE 32
#define ATMEL_ECC_PUBKEY_SIZE (2 * ATMEL_ECC_NIST_P256_N_SIZE)
+#define ATMEL_I2C_GENKEY_RSP_SIZE (ATMEL_ECC_PUBKEY_SIZE + \
+ ATMEL_I2C_RSP_OVERHEAD_SIZE)
+#define ATMEL_I2C_MAX_RSP_SIZE ATMEL_I2C_GENKEY_RSP_SIZE
-#define STATUS_RSP_SIZE 4
-#define ECDH_RSP_SIZE (32 + CMD_OVERHEAD_SIZE)
-#define GENKEY_RSP_SIZE (ATMEL_ECC_PUBKEY_SIZE + \
- CMD_OVERHEAD_SIZE)
-#define ATMEL_I2C_READ_RSP_SIZE (4 + CMD_OVERHEAD_SIZE)
-#define RANDOM_RSP_SIZE (32 + CMD_OVERHEAD_SIZE)
-#define MAX_RSP_SIZE GENKEY_RSP_SIZE
+/* Definitions for Indexes common to all commands */
+#define ATMEL_I2C_RSP_DATA_IDX 1 /* buffer index of data in response */
+#define ATMEL_I2C_ECDH_SLOT_DEFAULT 2
/**
* atmel_i2c_cmd - structure used for communicating with the device.
@@ -51,7 +62,7 @@ struct atmel_i2c_cmd {
u8 opcode;
u8 param1;
__le16 param2;
- u8 data[MAX_RSP_SIZE];
+ u8 data[ATMEL_I2C_MAX_RSP_SIZE];
u8 msecs;
u16 rxsize;
} __packed;
@@ -77,39 +88,6 @@ struct atmel_i2c_of_match_data {
size_t eeprom_zone_size[3]; /* all atmel devices have three zones */
};
-/* Status/Error codes */
-#define STATUS_SIZE 0x04
-#define STATUS_NOERR 0x00
-#define STATUS_WAKE_SUCCESSFUL 0x11
-
-/* Definitions for Indexes common to all commands */
-#define ATMEL_I2C_RSP_DATA_IDX 1 /* buffer index of data in response */
-#define DATA_SLOT_2 2 /* used for ECDH private key */
-
-/*
- * Wake High delay to data communication (microseconds). SDA should be stable
- * high for this entire duration.
- */
-#define TWHI_MIN 1500
-#define TWHI_MAX 1550
-
-/* Wake Low duration */
-#define TWLO_USEC 60
-
-/* Definitions for the READ Command */
-#define ATMEL_I2C_READ_COUNT 7
-
-/* Definitions for the RANDOM Command */
-#define RANDOM_COUNT 7
-
-/* Definitions for the GenKey Command */
-#define GENKEY_COUNT 7
-#define GENKEY_MODE_PRIVATE 0x04
-
-/* Definitions for the ECDH Command */
-#define ECDH_COUNT 71
-#define ECDH_PREFIX_MODE 0x00
-
/* Used for binding tfm objects to i2c clients. */
enum atmel_i2c_capability {
ATMEL_CAP_ECDH = 0,
@@ -144,7 +122,7 @@ struct atmel_i2c_client_priv {
struct i2c_client *client;
struct list_head i2c_client_list_node;
struct mutex lock;
- u8 wake_token[WAKE_TOKEN_MAX_SIZE];
+ u8 wake_token[ATMEL_I2C_WAKE_TOKEN_MAX_SIZE];
size_t wake_token_sz;
atomic_t tfm_count ____cacheline_aligned;
struct hwrng hwrng;
--
2.53.0
^ permalink raw reply related
* [PATCH 12/12] crypto: atmel - add SHA256 ahash support
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Add SHA256 ahash support for ATSHA204A and ECC based devices using
the hardware SHA engine provided by the Atmel secure element devices.
Implement common SHA256 request handling in the atmel-i2c core driver,
including init, update, final, finup, digest, export and import
operations. Scatterlist input is processed using the crypto hash
walker.
ATSHA204A devices require software-side SHA256 padding according to
FIPS 180-4 before submitting the final data blocks to the device.
Newer ECC devices instead support a dedicated SHA final command which
performs padding internally in hardware. For these devices, the final
block length is passed through the command parameter field.
The SHA engine requires a strict multi-command transaction sequence:
SHA INIT, followed by one or more SHA COMPUTE operations and, on ECC
devices, a terminating SHA FINAL operation. The device SHA context is
lost if the device enters sleep mode or if unrelated commands are
interleaved during the sequence.
To support these hardware requirements, split the existing
send/receive helper into a low-level transfer helper and a higher
level wrapper handling wakeup, sleep and locking. SHA operations keep
the device awake and hold the i2c client lock across the complete hash
transaction until the final digest has been retrieved.
Register the SHA256 ahash algorithm in both atmel-sha204a and
atmel-ecc drivers and add capability based client allocation for SHA
operations.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 50 +++++-
drivers/crypto/atmel-i2c.c | 273 +++++++++++++++++++++++++++++++--
drivers/crypto/atmel-i2c.h | 40 +++++
drivers/crypto/atmel-sha204a.c | 55 ++++++-
4 files changed, 407 insertions(+), 11 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index ed8c0ce5562b..aacf9e8add7a 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -19,10 +19,50 @@
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <crypto/internal/kpp.h>
+#include <crypto/internal/hash.h>
#include <crypto/ecdh.h>
#include <crypto/kpp.h>
+#include <crypto/sha2.h>
#include "atmel-i2c.h"
+static int atmel_ecc_sha_init_tfm(struct crypto_tfm *tfm)
+{
+ struct atmel_i2c_sha_ctx *ctx = crypto_tfm_ctx(tfm);
+
+ ctx->client = atmel_i2c_client_alloc(ATMEL_CAP_SHA);
+ if (IS_ERR(ctx->client)) {
+ pr_err("tfm - i2c_client binding failed\n");
+ return PTR_ERR(ctx->client);
+ }
+
+ return 0;
+}
+
+static struct ahash_alg atmel_ecc_sha = {
+ .init = atmel_i2c_sha_init,
+ .update = atmel_i2c_sha_update,
+ .final = atmel_i2c_sha_final,
+ .finup = atmel_i2c_sha_finup,
+ .digest = atmel_i2c_sha_digest,
+ .export = atmel_i2c_sha_export,
+ .import = atmel_i2c_sha_import,
+ .halg = {
+ .digestsize = SHA256_DIGEST_SIZE,
+ .statesize = sizeof(struct atmel_i2c_sha_reqctx),
+ .base = {
+ .cra_name = "sha256",
+ .cra_driver_name = "atmel-sha256",
+ .cra_init = atmel_ecc_sha_init_tfm,
+ .cra_priority = ATMEL_I2C_PRIORITY,
+ .cra_flags = CRYPTO_ALG_TYPE_AHASH,
+ .cra_blocksize = SHA256_BLOCK_SIZE,
+ .cra_ctxsize = sizeof(struct atmel_i2c_sha_ctx),
+ .cra_reqsize = sizeof(struct atmel_i2c_sha_reqctx),
+ .cra_module = THIS_MODULE,
+ }
+ }
+};
+
static ssize_t config_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return atmel_i2c_eeprom_display(dev, attr, buf, ATMEL_EEPROM_CONFIG_ZONE);
@@ -321,7 +361,7 @@ static int atmel_ecc_probe(struct i2c_client *client)
i2c_priv = i2c_get_clientdata(client);
i2c_priv->data = data;
- i2c_priv->caps = BIT(ATMEL_CAP_ECDH);
+ i2c_priv->caps = BIT(ATMEL_CAP_ECDH) | BIT(ATMEL_CAP_SHA);
ret = atmel_i2c_device_sanity_check(client);
if (ret) {
@@ -364,6 +404,12 @@ static int atmel_ecc_probe(struct i2c_client *client)
dev_info(&client->dev, "atmel ecc algorithms registered in /proc/crypto\n");
}
+ ret = crypto_register_ahash(&atmel_ecc_sha);
+ if (ret) {
+ dev_err(&client->dev, "SHA256 registration failed\n");
+ goto err_list_del;
+ }
+
goto done;
err_list_del:
@@ -392,6 +438,7 @@ static void atmel_ecc_remove(struct i2c_client *client)
atmel_i2c_flush_queue();
crypto_unregister_kpp(&atmel_ecdh_nist_p256);
+ crypto_unregister_ahash(&atmel_ecc_sha);
if (i2c_priv->hwrng.priv) {
kfree((void *)i2c_priv->hwrng.priv);
@@ -405,6 +452,7 @@ static const struct atmel_i2c_of_match_data atecc508a_match_data = {
.max_exec_time_genkey = 115,
.max_exec_time_random = 23,
.max_exec_time_read = 1,
+ .max_exec_time_sha = 9,
.max_exec_time_write = 42,
},
.eeprom_zone_size = {
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index 53aba2f4bedb..cbdc8c0e5aca 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -19,6 +19,10 @@
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
+#include <crypto/hash.h>
+#include <crypto/sha2.h>
+#include <crypto/internal/hash.h>
+
#include "atmel-i2c.h"
#define ATMEL_I2C_COMMAND 0x03 /* packet function */
@@ -49,12 +53,17 @@
#define ATMEL_I2C_ECDH_RSP_SIZE (32 + ATMEL_I2C_RSP_OVERHEAD_SIZE)
#define ATMEL_I2C_ECDH_PREFIX_MODE 0x00
+/* Definitions for the SHA Command */
+#define ATMEL_I2C_SHA_RSP_SIZE (ATMEL_I2C_RSP_OVERHEAD_SIZE + \
+ SHA256_DIGEST_SIZE)
+
/* Command opcode */
#define ATMEL_I2C_OPCODE_ECDH 0x43
#define ATMEL_I2C_OPCODE_GENKEY 0x40
#define ATMEL_I2C_OPCODE_READ 0x02
#define ATMEL_I2C_OPCODE_RANDOM 0x1b
#define ATMEL_I2C_OPCODE_WRITE 0x12
+#define ATMEL_I2C_OPCODE_SHA 0x47
/*
* Wake High delay to data communication (microseconds). SDA should be stable
@@ -244,6 +253,43 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
}
EXPORT_SYMBOL(atmel_i2c_init_ecdh_cmd);
+int atmel_i2c_init_sha_cmd(struct atmel_i2c_cmd *cmd,
+ u8 *challenge, size_t len,
+ enum atmel_i2c_sha_engine_cmd sha_engine_cmd,
+ const struct atmel_i2c_max_exec_timings *timings)
+{
+ cmd->word_addr = ATMEL_I2C_COMMAND;
+ cmd->opcode = ATMEL_I2C_OPCODE_SHA;
+ cmd->param1 = sha_engine_cmd;
+
+ cmd->param2 = cpu_to_le16(0);
+ /*
+ * Starting with the bigger ECCs, the device learned how to do SHA256
+ * padding (FIPS 180-4). Since SHA UPDATE always consumes 64B (SHA256
+ * block size), the only length needed to communicate is the number of
+ * used bytes in the final block. For the Atmel ECC series, this is
+ * passed in the param2.
+ */
+ if (sha_engine_cmd == atmel_sha_ecc_end)
+ cmd->param2 = cpu_to_le16(len);
+
+ cmd->count = ATMEL_I2C_COUNT_OVERHEAD_SIZE;
+ if (sha_engine_cmd == atmel_sha_init) {
+ memset(cmd->data, 0, sizeof(cmd->data));
+ } else {
+ memcpy(cmd->data, challenge, len);
+ cmd->count += len;
+ }
+
+ atmel_i2c_checksum(cmd);
+
+ cmd->msecs = timings->max_exec_time_sha;
+ cmd->rxsize = atmel_i2c_sha_rsp_size[sha_engine_cmd];
+
+ return 0;
+}
+EXPORT_SYMBOL(atmel_i2c_init_sha_cmd);
+
static void atmel_i2c_rng_done(struct atmel_i2c_work_data *work_data,
void *areq, int status)
{
@@ -492,21 +538,15 @@ static int atmel_i2c_sleep(struct i2c_client *client)
* counter other than to put the device into sleep or idle mode and then
* wake it up again.
*/
-int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd)
+static int _atmel_i2c_send_receive(struct i2c_client *client,
+ struct atmel_i2c_cmd *cmd)
{
- struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
int ret;
- mutex_lock(&i2c_priv->lock);
-
- ret = atmel_i2c_wakeup(client);
- if (ret)
- goto err;
-
/* send the command */
ret = i2c_master_send(client, (u8 *)cmd, cmd->count + ATMEL_I2C_ADDR_SIZE);
if (ret < 0)
- goto err;
+ return ret;
/* delay the appropriate amount of time for command to execute */
msleep(cmd->msecs);
@@ -514,6 +554,24 @@ int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd)
/* receive the response */
ret = i2c_master_recv(client, cmd->data, cmd->rxsize);
if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd)
+{
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
+ int ret;
+
+ mutex_lock(&i2c_priv->lock);
+
+ ret = atmel_i2c_wakeup(client);
+ if (ret)
+ goto err;
+
+ ret = _atmel_i2c_send_receive(client, cmd);
+ if (ret)
goto err;
/* put the device into low-power mode */
@@ -529,6 +587,203 @@ int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd)
}
EXPORT_SYMBOL(atmel_i2c_send_receive);
+int atmel_i2c_sha_init(struct ahash_request *req)
+{
+ struct atmel_i2c_sha_reqctx *rctx = ahash_request_ctx(req);
+ struct atmel_i2c_sha_ctx *ctx = crypto_tfm_ctx(req->base.tfm);
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(ctx->client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+ struct atmel_i2c_cmd *cmd;
+ int ret;
+
+ rctx->bufcnt = 0;
+ rctx->total = 0;
+ rctx->ctx = i2c_get_clientdata(ctx->client);
+
+ cmd = kmalloc_obj(*cmd);
+ if (!cmd)
+ return -ENOMEM;
+
+ /* SHA init */
+ ret = atmel_i2c_init_sha_cmd(cmd, NULL, 0, atmel_sha_init, &data->timings);
+ if (ret)
+ goto err_free;
+
+ mutex_lock(&i2c_priv->lock);
+
+ ret = atmel_i2c_wakeup(ctx->client);
+ if (ret)
+ goto err;
+
+ ret = _atmel_i2c_send_receive(ctx->client, cmd);
+ if (ret)
+ goto err;
+
+ /* we keep the lock hold until error out or _sha_final() is called */
+ return 0;
+err:
+ mutex_unlock(&i2c_priv->lock);
+err_free:
+ kfree_sensitive(cmd);
+ return ret;
+}
+EXPORT_SYMBOL(atmel_i2c_sha_init);
+
+int atmel_i2c_sha_update(struct ahash_request *req)
+{
+ struct atmel_i2c_sha_reqctx *rctx = ahash_request_ctx(req);
+ struct atmel_i2c_sha_ctx *ctx = crypto_tfm_ctx(req->base.tfm);
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(ctx->client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+ struct atmel_i2c_cmd *cmd;
+ struct crypto_hash_walk walk;
+ int nbytes, take, copied = 0;
+ const u8 *pdata;
+ int ret;
+
+ rctx->total += req->nbytes;
+
+ cmd = kmalloc_obj(*cmd);
+ if (!cmd) {
+ ret = -ENOMEM;
+ goto err_nomem;
+ }
+
+ /*
+ * Note, we are actively holding the i2c_priv->lock while the SHA engine
+ * operates. This covers init, update and final steps.
+ */
+ nbytes = crypto_hash_walk_first(req, &walk);
+ for (; nbytes > 0; nbytes = crypto_hash_walk_done(&walk, copied)) {
+ copied = nbytes;
+ pdata = walk.data;
+ while (copied > 0) {
+ take = min(copied, SHA256_BLOCK_SIZE - rctx->bufcnt);
+
+ memcpy(rctx->buffer + rctx->bufcnt, pdata, take);
+ pdata += take;
+ copied -= take;
+ rctx->bufcnt += take;
+ if (rctx->bufcnt == SHA256_BLOCK_SIZE) {
+ ret = atmel_i2c_init_sha_cmd(cmd, rctx->buffer,
+ SHA256_BLOCK_SIZE,
+ atmel_sha_compute,
+ &data->timings);
+ if (ret)
+ goto err;
+
+ ret = _atmel_i2c_send_receive(ctx->client, cmd);
+ if (ret)
+ goto err;
+
+ rctx->bufcnt = 0;
+ }
+ }
+ }
+
+ kfree_sensitive(cmd);
+ return 0;
+err:
+ kfree_sensitive(cmd);
+err_nomem:
+ mutex_unlock(&i2c_priv->lock);
+ return ret;
+}
+EXPORT_SYMBOL(atmel_i2c_sha_update);
+
+int atmel_i2c_sha_final(struct ahash_request *req)
+{
+ struct atmel_i2c_sha_reqctx *rctx = ahash_request_ctx(req);
+ struct atmel_i2c_sha_ctx *ctx = crypto_tfm_ctx(req->base.tfm);
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(ctx->client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
+ struct atmel_i2c_cmd *cmd;
+ u8 final_blocks[2 * SHA256_BLOCK_SIZE];
+ u32 total_pad;
+ __be64 bits;
+ int i, ret = 0;
+
+ cmd = kmalloc_obj(*cmd);
+ if (!cmd) {
+ ret = -ENOMEM;
+ goto err_nomem;
+ }
+
+ if (data->needs_sha_padding) {
+ /*
+ * Determine if padding fits in current block or needs another,
+ * SHA256 needs 8 bytes for length at the end of a 64-byte block.
+ */
+ memset(final_blocks, 0, sizeof(final_blocks));
+ memcpy(final_blocks, rctx->buffer, rctx->bufcnt);
+ final_blocks[rctx->bufcnt] = 0x80; /* pad bit */
+ total_pad = SHA256_BLOCK_SIZE * (rctx->bufcnt < 56 ? 1 : 2);
+ bits = cpu_to_be64((u64)rctx->total << 3); /* needs num of bits */
+ memcpy(final_blocks + total_pad - 8, &bits, 8);
+ for (i = 0; i < total_pad; i += SHA256_BLOCK_SIZE) {
+ ret = atmel_i2c_init_sha_cmd(cmd, final_blocks + i,
+ SHA256_BLOCK_SIZE,
+ atmel_sha_compute, &data->timings);
+ if (ret)
+ goto err_or_done;
+
+ ret = _atmel_i2c_send_receive(ctx->client, cmd);
+ if (ret)
+ goto err_or_done;
+ }
+ } else {
+ ret = atmel_i2c_init_sha_cmd(cmd, rctx->buffer, rctx->bufcnt,
+ atmel_sha_ecc_end, &data->timings);
+ if (ret)
+ goto err_or_done;
+
+ ret = _atmel_i2c_send_receive(ctx->client, cmd);
+ if (ret)
+ goto err_or_done;
+ }
+
+ memcpy(req->result, &cmd->data[ATMEL_I2C_RSP_DATA_IDX],
+ SHA256_DIGEST_SIZE);
+
+ /* Sleep returns a positive int on success, API requires 0 on success */
+ ret = atmel_i2c_sleep(ctx->client);
+ if (ret < 0)
+ goto err_or_done;
+ ret = 0;
+err_or_done:
+ kfree_sensitive(cmd);
+err_nomem:
+ mutex_unlock(&i2c_priv->lock);
+ return ret;
+}
+EXPORT_SYMBOL(atmel_i2c_sha_final);
+
+int atmel_i2c_sha_finup(struct ahash_request *req)
+{
+ return atmel_i2c_sha_update(req) ? : atmel_i2c_sha_final(req);
+}
+EXPORT_SYMBOL(atmel_i2c_sha_finup);
+
+int atmel_i2c_sha_digest(struct ahash_request *req)
+{
+ return atmel_i2c_sha_init(req) ? : atmel_i2c_sha_finup(req);
+}
+EXPORT_SYMBOL(atmel_i2c_sha_digest);
+
+int atmel_i2c_sha_export(struct ahash_request *req, void *out)
+{
+ memcpy(out, ahash_request_ctx(req), sizeof(struct atmel_i2c_sha_reqctx));
+ return 0;
+}
+EXPORT_SYMBOL(atmel_i2c_sha_export);
+
+int atmel_i2c_sha_import(struct ahash_request *req, const void *in)
+{
+ memcpy(ahash_request_ctx(req), in, sizeof(struct atmel_i2c_sha_reqctx));
+ return 0;
+}
+EXPORT_SYMBOL(atmel_i2c_sha_import);
+
static void atmel_i2c_work_handler(struct work_struct *work)
{
struct atmel_i2c_work_data *work_data =
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index 20afe2da4f8d..e0021d4ea686 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -7,8 +7,11 @@
#ifndef __ATMEL_I2C_H__
#define __ATMEL_I2C_H__
+#include <linux/device.h>
+#include <crypto/internal/hash.h>
#include <linux/hw_random.h>
#include <linux/types.h>
+#include <crypto/sha2.h>
#define ATMEL_I2C_PRIORITY 300
@@ -79,11 +82,13 @@ struct atmel_i2c_max_exec_timings {
unsigned int max_exec_time_ecdh;
unsigned int max_exec_time_random;
unsigned int max_exec_time_read;
+ unsigned int max_exec_time_sha;
unsigned int max_exec_time_write;
};
struct atmel_i2c_of_match_data {
const unsigned short needs_legacy_hwrng;
+ const unsigned short needs_sha_padding;
struct atmel_i2c_max_exec_timings timings;
size_t eeprom_zone_size[3]; /* all atmel devices have three zones */
};
@@ -91,6 +96,30 @@ struct atmel_i2c_of_match_data {
/* Used for binding tfm objects to i2c clients. */
enum atmel_i2c_capability {
ATMEL_CAP_ECDH = 0,
+ ATMEL_CAP_SHA,
+};
+
+enum atmel_i2c_sha_engine_cmd {
+ atmel_sha_init = 0,
+ atmel_sha_compute,
+ atmel_sha_ecc_end,
+};
+
+size_t atmel_i2c_sha_rsp_size[] = {
+ [atmel_sha_init] = ATMEL_I2C_STATUS_RSP_SIZE,
+ [atmel_sha_compute] = SHA256_DIGEST_SIZE + ATMEL_I2C_RSP_OVERHEAD_SIZE,
+ [atmel_sha_ecc_end] = SHA256_DIGEST_SIZE + ATMEL_I2C_RSP_OVERHEAD_SIZE,
+};
+
+struct atmel_i2c_sha_ctx {
+ struct i2c_client *client;
+};
+
+struct atmel_i2c_sha_reqctx {
+ u8 buffer[SHA256_BLOCK_SIZE];
+ size_t bufcnt;
+ size_t total; /* size of full input, needed for padding */
+ struct atmel_i2c_client_priv *ctx;
};
struct atmel_i2c_client_mgmt {
@@ -172,9 +201,20 @@ void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
struct scatterlist *pubkey,
const struct atmel_i2c_max_exec_timings *timings);
+int atmel_i2c_init_sha_cmd(struct atmel_i2c_cmd *cmd, u8 *challenge, size_t len,
+ enum atmel_i2c_sha_engine_cmd sha_engine_cmd,
+ const struct atmel_i2c_max_exec_timings *timings);
int atmel_i2c_register_rng(struct atmel_i2c_client_priv *i2c_priv,
struct device *dev);
+int atmel_i2c_sha_init(struct ahash_request *req);
+int atmel_i2c_sha_update(struct ahash_request *req);
+int atmel_i2c_sha_final(struct ahash_request *req);
+int atmel_i2c_sha_finup(struct ahash_request *req);
+int atmel_i2c_sha_digest(struct ahash_request *req);
+int atmel_i2c_sha_export(struct ahash_request *req, void *out);
+int atmel_i2c_sha_import(struct ahash_request *req, const void *in);
+
int atmel_i2c_device_sanity_check(struct i2c_client *client);
ssize_t atmel_i2c_eeprom_display(struct device *dev,
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index 6a41024ae40d..74535480edeb 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -17,8 +17,48 @@
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/workqueue.h>
+#include <crypto/sha2.h>
+
#include "atmel-i2c.h"
+static int atmel_sha204a_sha_init_tfm(struct crypto_tfm *tfm)
+{
+ struct atmel_i2c_sha_ctx *ctx = crypto_tfm_ctx(tfm);
+
+ ctx->client = atmel_i2c_client_alloc(ATMEL_CAP_SHA);
+ if (IS_ERR(ctx->client)) {
+ pr_err("tfm - i2c_client binding failed\n");
+ return PTR_ERR(ctx->client);
+ }
+
+ return 0;
+}
+
+static struct ahash_alg atmel_sha204a_sha = {
+ .init = atmel_i2c_sha_init,
+ .update = atmel_i2c_sha_update,
+ .final = atmel_i2c_sha_final,
+ .finup = atmel_i2c_sha_finup,
+ .digest = atmel_i2c_sha_digest,
+ .export = atmel_i2c_sha_export,
+ .import = atmel_i2c_sha_import,
+ .halg = {
+ .digestsize = SHA256_DIGEST_SIZE,
+ .statesize = sizeof(struct atmel_i2c_sha_reqctx),
+ .base = {
+ .cra_name = "sha256",
+ .cra_driver_name = "atmel-sha256",
+ .cra_init = atmel_sha204a_sha_init_tfm,
+ .cra_priority = ATMEL_I2C_PRIORITY,
+ .cra_flags = CRYPTO_ALG_TYPE_AHASH,
+ .cra_blocksize = SHA256_BLOCK_SIZE,
+ .cra_ctxsize = sizeof(struct atmel_i2c_sha_ctx),
+ .cra_reqsize = sizeof(struct atmel_i2c_sha_reqctx),
+ .cra_module = THIS_MODULE,
+ }
+ }
+};
+
static ssize_t config_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return atmel_i2c_eeprom_display(dev, attr, buf, ATMEL_EEPROM_CONFIG_ZONE);
@@ -62,7 +102,7 @@ static int atmel_sha204a_probe(struct i2c_client *client)
i2c_priv = i2c_get_clientdata(client);
i2c_priv->data = data;
- i2c_priv->caps = 0;
+ i2c_priv->caps = BIT(ATMEL_CAP_SHA);
ret = atmel_i2c_device_sanity_check(client);
if (ret) {
@@ -95,6 +135,13 @@ static int atmel_sha204a_probe(struct i2c_client *client)
goto err_list_del;
}
+ /* register algorithms */
+ ret = crypto_register_ahash(&atmel_sha204a_sha);
+ if (ret) {
+ dev_err(&client->dev, "SHA256 registration failed\n");
+ goto err_list_del;
+ }
+
goto done;
err_list_del:
@@ -119,6 +166,8 @@ static void atmel_sha204a_remove(struct i2c_client *client)
devm_hwrng_unregister(&client->dev, &i2c_priv->hwrng);
atmel_i2c_flush_queue();
+ crypto_unregister_ahash(&atmel_sha204a_sha);
+
if (i2c_priv->hwrng.priv) {
kfree((void *)i2c_priv->hwrng.priv);
i2c_priv->hwrng.priv = 0;
@@ -130,6 +179,7 @@ static const struct atmel_i2c_of_match_data atsha204_match_data = {
.max_exec_time_genkey = 43,
.max_exec_time_random = 50,
.max_exec_time_read = 4,
+ .max_exec_time_sha = 22,
.max_exec_time_write = 42,
},
.eeprom_zone_size = {
@@ -142,6 +192,7 @@ static const struct atmel_i2c_of_match_data atsha204_match_data = {
* [1] https://www.metzdowd.com/pipermail/cryptography/2014-December/023858.html
*/
.needs_legacy_hwrng = 1,
+ .needs_sha_padding = 1,
};
static const struct atmel_i2c_of_match_data atsha204a_match_data = {
@@ -149,6 +200,7 @@ static const struct atmel_i2c_of_match_data atsha204a_match_data = {
.max_exec_time_genkey = 43,
.max_exec_time_random = 50,
.max_exec_time_read = 4,
+ .max_exec_time_sha = 22,
.max_exec_time_write = 42,
},
.eeprom_zone_size = {
@@ -156,6 +208,7 @@ static const struct atmel_i2c_of_match_data atsha204a_match_data = {
[ATMEL_EEPROM_OTP_ZONE] = 64,
[ATMEL_EEPROM_DATA_ZONE] = 512
},
+ .needs_sha_padding = 1,
};
static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = {
--
2.53.0
^ permalink raw reply related
* [PATCH 1/2] dt-bindings: firmware: google,gs101-acpm-ipc: document Exynos850 compatible
From: Alexey Klimov @ 2026-05-12 23:12 UTC (permalink / raw)
To: Sam Protsenko, Tudor Ambarus, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Krzysztof Kozlowski, Alim Akhtar
Cc: Peter Griffin, linux-samsung-soc, devicetree, linux-arm-kernel,
linux-kernel
In-Reply-To: <20260513-exynos850-acpm-firmware-support-v1-0-3858d097e433@linaro.org>
The Exynos850 SoC incorporates an APM co-processor. Communication with
this hardware block is done using the ACPM protocol, which handles IPC
messages for clocks, power, thermal management and PMIC control.
Dedicated compatible string is required for the Exynos850 because
its firmware utilizes a different initialisation data base offset
(0x7000) compared to the existing GS101 implementation (0xa000).
Signed-off-by: Alexey Klimov <alexey.klimov@linaro.org>
---
Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml b/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml
index e68f9c3ca5e2..511c873280fe 100644
--- a/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml
+++ b/Documentation/devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml
@@ -22,7 +22,9 @@ description: |
properties:
compatible:
- const: google,gs101-acpm-ipc
+ enum:
+ - google,gs101-acpm-ipc
+ - samsung,exynos850-acpm-ipc
"#clock-cells":
const: 1
--
2.51.0
^ permalink raw reply related
* [PATCH 0/2] Initial Exynos850 ACPM support for exynos-acpm
From: Alexey Klimov @ 2026-05-12 23:12 UTC (permalink / raw)
To: Sam Protsenko, Tudor Ambarus, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Krzysztof Kozlowski, Alim Akhtar
Cc: Peter Griffin, linux-samsung-soc, devicetree, linux-arm-kernel,
linux-kernel
This series adds support for the Exynos850 SoC to the Samsung ACPM
firmware IPC driver.
The Exynos850 contains an APM co-processor that handles IPC messages for
clocks, power, thermal management, and PMIC control. While it uses the
same underlying ACPM communication machinery and protocols set as
the GS101, the exact implementation details vary between the two SoCs.
Those protocol-specific differences will be managed individually by the
corresponding protocol drivers. However, a dedicated compatible string
is still required for the core IPC driver because the Exynos850 firmware
utilizes a different initialisation data base offset.
First patch updates the google,gs101-acpm-ipc dt-bindings to include the
new "samsung,exynos850-acpm-ipc" compatible string.
Second patch adds the corresponding match data, base offset, and clock
device name to the exynos-acpm firmware driver.
There is a dependency for driver changes (not for device tree bindings
update) on clk-acpm exynos850 series posted here:
https://lore.kernel.org/linux-samsung-soc/20260512-exynos850-acpm-clk-v1-0-837532ddbf38@linaro.org/
Signed-off-by: Alexey Klimov <alexey.klimov@linaro.org>
---
Alexey Klimov (2):
dt-bindings: firmware: google,gs101-acpm-ipc: document Exynos850 compatible
firmware: samsung: acpm: add Exynos850 support
.../devicetree/bindings/firmware/google,gs101-acpm-ipc.yaml | 4 +++-
drivers/firmware/samsung/exynos-acpm.c | 10 ++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
---
base-commit: 9e0898f1c0f134c6bad146ca8578f73c3e40ac0a
change-id: 20260512-exynos850-acpm-firmware-support-0aefaf4104b6
prerequisite-change-id: 20260512-exynos850-acpm-clk-bed1c23b66eb:v1
prerequisite-patch-id: 223c977406801dde31779f956e33e65e51a0323c
prerequisite-patch-id: 9b11f60e3c53e94e28b5e54fb7bb87e5415d8a05
Best regards,
--
Alexey Klimov <alexey.klimov@linaro.org>
^ permalink raw reply
* [PATCH 2/2] firmware: samsung: acpm: add Exynos850 support
From: Alexey Klimov @ 2026-05-12 23:12 UTC (permalink / raw)
To: Sam Protsenko, Tudor Ambarus, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Krzysztof Kozlowski, Alim Akhtar
Cc: Peter Griffin, linux-samsung-soc, devicetree, linux-arm-kernel,
linux-kernel
In-Reply-To: <20260513-exynos850-acpm-firmware-support-v1-0-3858d097e433@linaro.org>
The Exynos850 SoC contains an APM co-processor. Communication
with this hardware block is established using the ACPM protocol,
which handles IPC messages for clocks, power, thermal management,
and PMIC control.
Add the "samsung,exynos850-acpm-ipc" compatible string along with
its associated match data. This includes the specific initialisation
data base offset (which differs from the GS101 offset) and the
"exynos850-acpm-clk" device name required to properly instantiate
the clock provider.
Signed-off-by: Alexey Klimov <alexey.klimov@linaro.org>
---
drivers/firmware/samsung/exynos-acpm.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c
index 16c46ed60837..db4138f36038 100644
--- a/drivers/firmware/samsung/exynos-acpm.c
+++ b/drivers/firmware/samsung/exynos-acpm.c
@@ -37,6 +37,7 @@
#define ACPM_POLL_TIMEOUT_US (100 * USEC_PER_MSEC)
#define ACPM_TX_TIMEOUT_US 500000
+#define ACPM_EXYNOS850_INITDATA_BASE 0x7000
#define ACPM_GS101_INITDATA_BASE 0xa000
/**
@@ -766,6 +767,11 @@ struct acpm_handle *devm_acpm_get_by_node(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_acpm_get_by_node);
+static const struct acpm_match_data acpm_exynos850 = {
+ .initdata_base = ACPM_EXYNOS850_INITDATA_BASE,
+ .acpm_clk_dev_name = "exynos850-acpm-clk",
+};
+
static const struct acpm_match_data acpm_gs101 = {
.initdata_base = ACPM_GS101_INITDATA_BASE,
.acpm_clk_dev_name = "gs101-acpm-clk",
@@ -776,6 +782,10 @@ static const struct of_device_id acpm_match[] = {
.compatible = "google,gs101-acpm-ipc",
.data = &acpm_gs101,
},
+ {
+ .compatible = "samsung,exynos850-acpm-ipc",
+ .data = &acpm_exynos850,
+ },
{},
};
MODULE_DEVICE_TABLE(of, acpm_match);
--
2.51.0
^ permalink raw reply related
* Re: [PATCH net-next v7 2/4] net: stmmac: eic7700: enable clocks before syscon access and correct RX sampling timing
From: Jakub Kicinski @ 2026-05-12 23:13 UTC (permalink / raw)
To: 李志
Cc: andrew+netdev, devicetree, davem, edumazet, robh, krzk+dt,
conor+dt, netdev, pabeni, mcoquelin.stm32, alexandre.torgue,
rmk+kernel, pjw, palmer, aou, alex, linux-riscv, linux-stm32,
linux-arm-kernel, linux-kernel, maxime.chevallier, ningyu, linmin,
pinkesh.vaghela, pritesh.patel, weishangjuan, horms
In-Reply-To: <446f69bd.7fe4.19e1ab248fb.Coremail.lizhi2@eswincomputing.com>
On Tue, 12 May 2026 13:39:12 +0800 (GMT+08:00) 李志 wrote:
> For the eth1 enablement part, my current understanding is that it
> should be treated as a new independent v1 series for net-next,
> since the scope and target tree have changed after the split.
>
> Would you prefer this eth1 series to start as v1, or should it
> continue as v8 for continuity with the original series?
v8 is better, but is the fix in net-next already?
If this is the posting you're referring to:
https://lore.kernel.org/all/20260507083214.192-1-lizhi2@eswincomputing.com/
it has been dropped based on feedback from Maxime and I don't see a v2.
^ permalink raw reply
* [PATCH 09/12] crypto: atmel - check client data in remove callbacks
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Check the i2c client private data pointer in the remove callbacks of
the Atmel ECC and SHA204A drivers before accessing driver state.
Move sysfs group removal ahead of the NULL check so cleanup can still
proceed even if client data is unavailable. Also downgrade the
busy-device warning in the ECC remove path from dev_emerg() to
dev_warn().
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 20 ++++++--------------
drivers/crypto/atmel-sha204a.c | 7 +++++--
2 files changed, 11 insertions(+), 16 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index f6d1a9694d63..9ad6d42b6eef 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -380,19 +380,13 @@ static void atmel_ecc_remove(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
- /* Return EBUSY if i2c client already allocated. */
- if (atomic_read(&i2c_priv->tfm_count)) {
- /*
- * After we return here, the memory backing the device is freed.
- * That happens no matter what the return value of this function
- * is because in the Linux device model there is no error
- * handling for unbinding a driver.
- * If there is still some action pending, it probably involves
- * accessing the freed memory.
- */
- dev_emerg(&client->dev, "Device is busy, expect memory corruption.\n");
+ sysfs_remove_group(&client->dev.kobj, &atmel_ecc508a_groups);
+
+ if (!i2c_priv)
return;
- }
+
+ if (atomic_read(&i2c_priv->tfm_count))
+ dev_warn(&client->dev, "Device is busy, remove it anyhow\n");
atmel_i2c_unregister_client(i2c_priv);
atmel_i2c_flush_queue();
@@ -403,8 +397,6 @@ static void atmel_ecc_remove(struct i2c_client *client)
kfree((void *)i2c_priv->hwrng.priv);
i2c_priv->hwrng.priv = 0;
}
-
- sysfs_remove_group(&client->dev.kobj, &atmel_ecc508a_groups);
}
static const struct atmel_i2c_of_match_data atecc508a_match_data = {
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index 88726f6ef87c..6a41024ae40d 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -111,6 +111,11 @@ static void atmel_sha204a_remove(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
+ sysfs_remove_group(&client->dev.kobj, &atmel_sha204a_groups);
+
+ if (!i2c_priv)
+ return;
+
devm_hwrng_unregister(&client->dev, &i2c_priv->hwrng);
atmel_i2c_flush_queue();
@@ -118,8 +123,6 @@ static void atmel_sha204a_remove(struct i2c_client *client)
kfree((void *)i2c_priv->hwrng.priv);
i2c_priv->hwrng.priv = 0;
}
-
- sysfs_remove_group(&client->dev.kobj, &atmel_sha204a_groups);
}
static const struct atmel_i2c_of_match_data atsha204_match_data = {
--
2.53.0
^ permalink raw reply related
* [PATCH 04/12] crypto: atmel - add per-device timing and match-data driven configuration
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
The ATSHA204(A) and ATECC device families define different maximum
command execution times in their datasheets. The current driver uses a
mixed set of timing constants, which can result in insufficient wait
times for some devices.
Introduce struct atmel_i2c_of_match_data to provide per-device timing
information through the device match tables. Store the match data in the
client private structure and pass the timing parameters to the command
initialization helpers instead of relying on global timing constants.
This allows the common atmel-i2c core to use device-specific command
timeouts for operations such as READ, RANDOM, GENKEY, and ECDH.
Also move the legacy hwrng quality information into the match data
structure to consolidate per-device configuration in a single place.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 32 +++++++++++++---
drivers/crypto/atmel-i2c.c | 29 ++++++++------
drivers/crypto/atmel-i2c.h | 36 ++++++++++++------
drivers/crypto/atmel-sha204a.c | 69 ++++++++++++++++++++++++----------
4 files changed, 120 insertions(+), 46 deletions(-)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index 0dede3707b73..7793f7b4e97e 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -76,6 +76,8 @@ static int atmel_ecdh_set_secret(struct crypto_kpp *tfm, const void *buf,
unsigned int len)
{
struct atmel_ecdh_ctx *ctx = kpp_tfm_ctx(tfm);
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(ctx->client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
struct atmel_i2c_cmd *cmd;
void *public_key;
struct ecdh params;
@@ -112,7 +114,7 @@ static int atmel_ecdh_set_secret(struct crypto_kpp *tfm, const void *buf,
ctx->do_fallback = false;
- atmel_i2c_init_genkey_cmd(cmd, DATA_SLOT_2);
+ atmel_i2c_init_genkey_cmd(cmd, DATA_SLOT_2, &data->timings);
ret = atmel_i2c_send_receive(ctx->client, cmd);
if (ret)
@@ -164,6 +166,8 @@ static int atmel_ecdh_compute_shared_secret(struct kpp_request *req)
{
struct crypto_kpp *tfm = crypto_kpp_reqtfm(req);
struct atmel_ecdh_ctx *ctx = kpp_tfm_ctx(tfm);
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(ctx->client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
struct atmel_i2c_work_data *work_data;
gfp_t gfp;
int ret;
@@ -187,7 +191,7 @@ static int atmel_ecdh_compute_shared_secret(struct kpp_request *req)
work_data->ctx = ctx;
work_data->client = ctx->client;
- ret = atmel_i2c_init_ecdh_cmd(&work_data->cmd, req->src);
+ ret = atmel_i2c_init_ecdh_cmd(&work_data->cmd, req->src, &data->timings);
if (ret)
goto free_work_data;
@@ -278,14 +282,22 @@ static struct kpp_alg atmel_ecdh_nist_p256 = {
static int atmel_ecc_probe(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv;
+ const struct atmel_i2c_of_match_data *data;
int ret;
ret = atmel_i2c_probe(client);
if (ret)
goto done;
- i2c_priv = i2c_get_clientdata(client);
+ data = device_get_match_data(&client->dev);
+ if (!data) {
+ dev_err(&client->dev, "no match data found via OF or ID table\n");
+ ret = -ENODEV;
+ goto done;
+ }
+ i2c_priv = i2c_get_clientdata(client);
+ i2c_priv->data = data;
i2c_priv->caps = BIT(ATMEL_CAP_ECDH);
/* add to client list */
@@ -339,9 +351,19 @@ static void atmel_ecc_remove(struct i2c_client *client)
crypto_unregister_kpp(&atmel_ecdh_nist_p256);
}
+static const struct atmel_i2c_of_match_data atecc508a_match_data = {
+ .timings = {
+ .max_exec_time_ecdh = 58,
+ .max_exec_time_genkey = 115,
+ .max_exec_time_random = 23,
+ .max_exec_time_read = 1,
+ .max_exec_time_write = 42,
+ },
+};
+
static const struct of_device_id atmel_ecc_dt_ids[] = {
- { .compatible = "atmel,atecc508a", },
- { .compatible = "atmel,atecc608b", },
+ { .compatible = "atmel,atecc508a", .data = &atecc508a_match_data, },
+ { .compatible = "atmel,atecc608b", .data = &atecc508a_match_data, },
{ }
};
MODULE_DEVICE_TABLE(of, atmel_ecc_dt_ids);
diff --git a/drivers/crypto/atmel-i2c.c b/drivers/crypto/atmel-i2c.c
index b7ee2ec37531..7fa7cf9ab3c1 100644
--- a/drivers/crypto/atmel-i2c.c
+++ b/drivers/crypto/atmel-i2c.c
@@ -96,7 +96,8 @@ struct i2c_client *atmel_i2c_client_alloc(enum atmel_i2c_capability cap)
}
EXPORT_SYMBOL(atmel_i2c_client_alloc);
-void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd)
+void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd,
+ const struct atmel_i2c_max_exec_timings *timings)
{
cmd->word_addr = COMMAND;
cmd->opcode = OPCODE_READ;
@@ -110,12 +111,13 @@ void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd)
atmel_i2c_checksum(cmd);
- cmd->msecs = MAX_EXEC_TIME_READ;
+ cmd->msecs = timings->max_exec_time_read;
cmd->rxsize = READ_RSP_SIZE;
}
EXPORT_SYMBOL(atmel_i2c_init_read_config_cmd);
-int atmel_i2c_init_read_otp_cmd(struct atmel_i2c_cmd *cmd, u16 addr)
+int atmel_i2c_init_read_otp_cmd(struct atmel_i2c_cmd *cmd, u16 addr,
+ const struct atmel_i2c_max_exec_timings *timings)
{
if (addr >= OTP_ZONE_SIZE / 4)
return -EINVAL;
@@ -132,14 +134,15 @@ int atmel_i2c_init_read_otp_cmd(struct atmel_i2c_cmd *cmd, u16 addr)
atmel_i2c_checksum(cmd);
- cmd->msecs = MAX_EXEC_TIME_READ;
+ cmd->msecs = timings->max_exec_time_read;
cmd->rxsize = READ_RSP_SIZE;
return 0;
}
EXPORT_SYMBOL(atmel_i2c_init_read_otp_cmd);
-void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd)
+void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd,
+ const struct atmel_i2c_max_exec_timings *timings)
{
cmd->word_addr = COMMAND;
cmd->opcode = OPCODE_RANDOM;
@@ -149,12 +152,13 @@ void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd)
atmel_i2c_checksum(cmd);
- cmd->msecs = MAX_EXEC_TIME_RANDOM;
+ cmd->msecs = timings->max_exec_time_random;
cmd->rxsize = RANDOM_RSP_SIZE;
}
EXPORT_SYMBOL(atmel_i2c_init_random_cmd);
-void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid)
+void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
+ const struct atmel_i2c_max_exec_timings *timings)
{
cmd->word_addr = COMMAND;
cmd->count = GENKEY_COUNT;
@@ -165,13 +169,14 @@ void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid)
atmel_i2c_checksum(cmd);
- cmd->msecs = MAX_EXEC_TIME_GENKEY;
+ cmd->msecs = timings->max_exec_time_genkey;
cmd->rxsize = GENKEY_RSP_SIZE;
}
EXPORT_SYMBOL(atmel_i2c_init_genkey_cmd);
int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
- struct scatterlist *pubkey)
+ struct scatterlist *pubkey,
+ const struct atmel_i2c_max_exec_timings *timings)
{
size_t copied;
@@ -196,7 +201,7 @@ int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
atmel_i2c_checksum(cmd);
- cmd->msecs = MAX_EXEC_TIME_ECDH;
+ cmd->msecs = timings->max_exec_time_ecdh;
cmd->rxsize = ECDH_RSP_SIZE;
return 0;
@@ -363,6 +368,8 @@ static inline size_t atmel_i2c_wake_token_sz(u32 bus_clk_rate)
static int device_sanity_check(struct i2c_client *client)
{
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
struct atmel_i2c_cmd *cmd;
int ret;
@@ -370,7 +377,7 @@ static int device_sanity_check(struct i2c_client *client)
if (!cmd)
return -ENOMEM;
- atmel_i2c_init_read_config_cmd(cmd);
+ atmel_i2c_init_read_config_cmd(cmd, &data->timings);
ret = atmel_i2c_send_receive(client, cmd);
if (ret)
diff --git a/drivers/crypto/atmel-i2c.h b/drivers/crypto/atmel-i2c.h
index 70579b438256..5224a62c16c9 100644
--- a/drivers/crypto/atmel-i2c.h
+++ b/drivers/crypto/atmel-i2c.h
@@ -57,6 +57,19 @@ struct atmel_i2c_cmd {
u16 rxsize;
} __packed;
+struct atmel_i2c_max_exec_timings {
+ unsigned int max_exec_time_genkey;
+ unsigned int max_exec_time_ecdh;
+ unsigned int max_exec_time_random;
+ unsigned int max_exec_time_read;
+ unsigned int max_exec_time_write;
+};
+
+struct atmel_i2c_of_match_data {
+ const unsigned short *legacy_hwrng;
+ struct atmel_i2c_max_exec_timings timings;
+};
+
/* Status/Error codes */
#define STATUS_SIZE 0x04
#define STATUS_NOERR 0x00
@@ -88,12 +101,6 @@ struct atmel_i2c_cmd {
/* Wake Low duration */
#define TWLO_USEC 60
-/* Command execution time (milliseconds) */
-#define MAX_EXEC_TIME_ECDH 58
-#define MAX_EXEC_TIME_GENKEY 115
-#define MAX_EXEC_TIME_READ 1
-#define MAX_EXEC_TIME_RANDOM 50
-
/* Command opcode */
#define OPCODE_ECDH 0x43
#define OPCODE_GENKEY 0x40
@@ -135,6 +142,7 @@ extern struct atmel_i2c_client_mgmt atmel_i2c_mgmt;
* @tfm_count : number of active crypto transformations on i2c client
* @hwrng : hold the hardware generated rng
* @caps : feature capability of the particular driver
+ * @data : preinitialized driver data
*
* Reads and writes from/to the i2c client are sequential. The first byte
* transmitted to the device is treated as the byte size. Any attempt to send
@@ -152,6 +160,7 @@ struct atmel_i2c_client_priv {
atomic_t tfm_count ____cacheline_aligned;
struct hwrng hwrng;
u32 caps;
+ const struct atmel_i2c_of_match_data *data;
};
/**
@@ -189,12 +198,17 @@ void atmel_i2c_flush_queue(void);
int atmel_i2c_send_receive(struct i2c_client *client, struct atmel_i2c_cmd *cmd);
-void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd);
-int atmel_i2c_init_read_otp_cmd(struct atmel_i2c_cmd *cmd, u16 addr);
-void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd);
-void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid);
+void atmel_i2c_init_read_config_cmd(struct atmel_i2c_cmd *cmd,
+ const struct atmel_i2c_max_exec_timings *timings);
+int atmel_i2c_init_read_otp_cmd(struct atmel_i2c_cmd *cmd, u16 addr,
+ const struct atmel_i2c_max_exec_timings *timings);
+void atmel_i2c_init_random_cmd(struct atmel_i2c_cmd *cmd,
+ const struct atmel_i2c_max_exec_timings *timings);
+void atmel_i2c_init_genkey_cmd(struct atmel_i2c_cmd *cmd, u16 keyid,
+ const struct atmel_i2c_max_exec_timings *timings);
int atmel_i2c_init_ecdh_cmd(struct atmel_i2c_cmd *cmd,
- struct scatterlist *pubkey);
+ struct scatterlist *pubkey,
+ const struct atmel_i2c_max_exec_timings *timings);
struct i2c_client *atmel_i2c_client_alloc(enum atmel_i2c_capability cap);
void atmel_i2c_unregister_client(struct atmel_i2c_client_priv *i2c_priv);
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index ab758c9cd410..febf9891b167 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -40,14 +40,15 @@ static void atmel_sha204a_rng_done(struct atmel_i2c_work_data *work_data,
atomic_dec(&i2c_priv->tfm_count);
}
-static int atmel_sha204a_rng_read_nonblocking(struct hwrng *rng, void *data,
+static int atmel_sha204a_rng_read_nonblocking(struct hwrng *rng, void *buf,
size_t max)
{
- struct atmel_i2c_client_priv *i2c_priv;
+ struct atmel_i2c_client_priv *i2c_priv = container_of(rng,
+ struct atmel_i2c_client_priv,
+ hwrng);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
struct atmel_i2c_work_data *work_data;
- i2c_priv = container_of(rng, struct atmel_i2c_client_priv, hwrng);
-
/* keep maximum 1 asynchronous read in flight at any time */
if (!atomic_add_unless(&i2c_priv->tfm_count, 1, 1))
return 0;
@@ -55,7 +56,7 @@ static int atmel_sha204a_rng_read_nonblocking(struct hwrng *rng, void *data,
if (rng->priv) {
work_data = (struct atmel_i2c_work_data *)rng->priv;
max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
- memcpy(data, &work_data->cmd.data[RSP_DATA_IDX], max);
+ memcpy(buf, &work_data->cmd.data[RSP_DATA_IDX], max);
rng->priv = 0;
} else {
work_data = kmalloc_obj(*work_data, GFP_ATOMIC);
@@ -69,42 +70,45 @@ static int atmel_sha204a_rng_read_nonblocking(struct hwrng *rng, void *data,
max = 0;
}
- atmel_i2c_init_random_cmd(&work_data->cmd);
+ atmel_i2c_init_random_cmd(&work_data->cmd, &data->timings);
atmel_i2c_enqueue(work_data, atmel_sha204a_rng_done, rng);
return max;
}
-static int atmel_sha204a_rng_read(struct hwrng *rng, void *data, size_t max,
+static int atmel_sha204a_rng_read(struct hwrng *rng, void *buf, size_t max,
bool wait)
{
- struct atmel_i2c_client_priv *i2c_priv;
+ struct atmel_i2c_client_priv *i2c_priv = container_of(rng,
+ struct atmel_i2c_client_priv,
+ hwrng);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
struct atmel_i2c_cmd cmd;
int ret;
if (!wait)
- return atmel_sha204a_rng_read_nonblocking(rng, data, max);
-
- i2c_priv = container_of(rng, struct atmel_i2c_client_priv, hwrng);
+ return atmel_sha204a_rng_read_nonblocking(rng, buf, max);
- atmel_i2c_init_random_cmd(&cmd);
+ atmel_i2c_init_random_cmd(&cmd, &data->timings);
ret = atmel_i2c_send_receive(i2c_priv->client, &cmd);
if (ret)
return ret;
max = min(RANDOM_RSP_SIZE - CMD_OVERHEAD_SIZE, max);
- memcpy(data, &cmd.data[RSP_DATA_IDX], max);
+ memcpy(buf, &cmd.data[RSP_DATA_IDX], max);
return max;
}
static int atmel_sha204a_otp_read(struct i2c_client *client, u16 addr, u8 *otp)
{
+ struct atmel_i2c_client_priv *i2c_priv = i2c_get_clientdata(client);
+ const struct atmel_i2c_of_match_data *data = i2c_priv->data;
struct atmel_i2c_cmd cmd;
int ret;
- ret = atmel_i2c_init_read_otp_cmd(&cmd, addr);
+ ret = atmel_i2c_init_read_otp_cmd(&cmd, addr, &data->timings);
if (ret < 0) {
dev_err(&client->dev, "failed, invalid otp address %04X\n",
addr);
@@ -164,6 +168,7 @@ static const struct attribute_group atmel_sha204a_groups = {
static int atmel_sha204a_probe(struct i2c_client *client)
{
struct atmel_i2c_client_priv *i2c_priv;
+ const struct atmel_i2c_of_match_data *data;
const unsigned short *quality;
int ret;
@@ -171,8 +176,15 @@ static int atmel_sha204a_probe(struct i2c_client *client)
if (ret)
goto done;
- i2c_priv = i2c_get_clientdata(client);
+ data = device_get_match_data(&client->dev);
+ if (!data) {
+ dev_err(&client->dev, "no match data found via OF or ID table\n");
+ ret = -ENODEV;
+ goto done;
+ }
+ i2c_priv = i2c_get_clientdata(client);
+ i2c_priv->data = data;
i2c_priv->caps = 0;
/* add to client list */
@@ -187,7 +199,7 @@ static int atmel_sha204a_probe(struct i2c_client *client)
i2c_priv->hwrng.name = dev_name(&client->dev);
i2c_priv->hwrng.read = atmel_sha204a_rng_read;
- quality = i2c_get_match_data(client);
+ quality = i2c_priv->data->legacy_hwrng;
if (quality)
i2c_priv->hwrng.quality = *quality;
@@ -227,15 +239,34 @@ static void atmel_sha204a_remove(struct i2c_client *client)
kfree((void *)i2c_priv->hwrng.priv);
}
+static const struct atmel_i2c_of_match_data atsha204_match_data = {
+ .timings = {
+ .max_exec_time_genkey = 43,
+ .max_exec_time_random = 50,
+ .max_exec_time_read = 4,
+ .max_exec_time_write = 42,
+ },
+ .legacy_hwrng = &atsha204_quality,
+};
+
+static const struct atmel_i2c_of_match_data atsha204a_match_data = {
+ .timings = {
+ .max_exec_time_genkey = 43,
+ .max_exec_time_random = 50,
+ .max_exec_time_read = 4,
+ .max_exec_time_write = 42,
+ },
+};
+
static const struct of_device_id atmel_sha204a_dt_ids[] __maybe_unused = {
- { .compatible = "atmel,atsha204", .data = &atsha204_quality },
- { .compatible = "atmel,atsha204a", },
+ { .compatible = "atmel,atsha204", .data = &atsha204_match_data, },
+ { .compatible = "atmel,atsha204a", .data = &atsha204a_match_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atmel_sha204a_dt_ids);
static const struct i2c_device_id atmel_sha204a_id[] = {
- { "atsha204", (kernel_ulong_t)&atsha204_quality },
+ { "atsha204" },
{ "atsha204a" },
{ /* sentinel */ }
};
--
2.53.0
^ permalink raw reply related
* [PATCH 07/12] crypto: atmel - expose CONFIG zone through sysfs
From: Lothar Rubusch @ 2026-05-12 22:43 UTC (permalink / raw)
To: thorsten.blum, herbert, davem, nicolas.ferre, alexandre.belloni,
claudiu.beznea
Cc: linux-crypto, linux-arm-kernel, linux-kernel, l.rubusch
In-Reply-To: <20260512224349.64621-1-l.rubusch@gmail.com>
Expose the CONFIG EEPROM zone through a read-only sysfs attribute for
Atmel I2C crypto devices.
The CONFIG zone contains device configuration state, including slot
configuration and lock status, which is useful for debugging and
verifying provisioning state.
Reuse the generic EEPROM display helper provided by the Atmel I2C core
driver to expose the CONFIG zone for both SHA204A and ECC devices.
Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
---
drivers/crypto/atmel-ecc.c | 7 +++++++
drivers/crypto/atmel-sha204a.c | 7 +++++++
2 files changed, 14 insertions(+)
diff --git a/drivers/crypto/atmel-ecc.c b/drivers/crypto/atmel-ecc.c
index b5f2d44ec74c..f08fdf284b60 100644
--- a/drivers/crypto/atmel-ecc.c
+++ b/drivers/crypto/atmel-ecc.c
@@ -23,6 +23,12 @@
#include <crypto/kpp.h>
#include "atmel-i2c.h"
+static ssize_t config_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return atmel_i2c_eeprom_display(dev, attr, buf, ATMEL_EEPROM_CONFIG_ZONE);
+}
+static DEVICE_ATTR_ADMIN_RO(config);
+
static ssize_t otp_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return atmel_i2c_eeprom_display(dev, attr, buf, ATMEL_EEPROM_OTP_ZONE);
@@ -30,6 +36,7 @@ static ssize_t otp_show(struct device *dev, struct device_attribute *attr, char
static DEVICE_ATTR_RO(otp);
static struct attribute *atmel_ecc508a_attrs[] = {
+ &dev_attr_config.attr,
&dev_attr_otp.attr,
NULL
};
diff --git a/drivers/crypto/atmel-sha204a.c b/drivers/crypto/atmel-sha204a.c
index 4f10e826e675..341554b7b7a2 100644
--- a/drivers/crypto/atmel-sha204a.c
+++ b/drivers/crypto/atmel-sha204a.c
@@ -19,6 +19,12 @@
#include <linux/workqueue.h>
#include "atmel-i2c.h"
+static ssize_t config_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return atmel_i2c_eeprom_display(dev, attr, buf, ATMEL_EEPROM_CONFIG_ZONE);
+}
+static DEVICE_ATTR_ADMIN_RO(config);
+
static ssize_t otp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -27,6 +33,7 @@ static ssize_t otp_show(struct device *dev,
static DEVICE_ATTR_RO(otp);
static struct attribute *atmel_sha204a_attrs[] = {
+ &dev_attr_config.attr,
&dev_attr_otp.attr,
NULL
};
--
2.53.0
^ permalink raw reply related
* [PATCH v4 0/3] iio: adc: xilinx-ams: refactor alarm handling to table-driven design
From: Guilherme Ivo Bozi @ 2026-05-13 0:31 UTC (permalink / raw)
To: anand.ashok.dumbre, andy, conall.ogriofa, dlechner, jic23,
manish.narani, michal.simek, nuno.sa, salih.erim
Cc: Guilherme Ivo Bozi, Jonathan.Cameron, linux-arm-kernel, linux-iio
This series addresses significant code duplication in alarm handling
logic across the Xilinx AMS IIO driver.
To address this, the series introduces a centralized table-driven
mapping (alarm_map) that replaces multiple switch statements spread
across the driver.
This improves:
- maintainability (single source of truth for mappings)
- readability (removes repeated switch logic)
- extensibility (new alarms require only table updates)
No functional changes are intended.
Series overview:
- Patch 1: fix out-of-bounds channel lookup
- Patch 2: convert mutex handling to guard(mutex)
- Patch 3: introduce table-driven alarm mapping
v1 -> v2:
- Fixed Fixes tag format
- Replaced AMS_ALARM_INVALID with AMS_ALARM_NONE
- Changed alarm_map base_offset type
v2 -> v3:
- Replace 'i >= num_channels' with 'i == num_channels'
- Add missing trailing comma in alarm_map array initializer
v3 -> v4:
- Removed unnecessary 'event < 0' check for type e32
Guilherme Ivo Bozi (3):
iio: adc: xilinx-ams: fix out-of-bounds channel lookup in event
handling
iio: adc: xilinx-ams: use guard(mutex) for automatic locking
iio: adc: xilinx-ams: refactor alarm mapping to table-driven approach
drivers/iio/adc/xilinx-ams.c | 190 +++++++++++++----------------------
1 file changed, 71 insertions(+), 119 deletions(-)
--
2.47.3
^ permalink raw reply
* [PATCH v4 1/3] iio: adc: xilinx-ams: fix out-of-bounds channel lookup in event handling
From: Guilherme Ivo Bozi @ 2026-05-13 0:31 UTC (permalink / raw)
To: anand.ashok.dumbre, andy, conall.ogriofa, dlechner, jic23,
manish.narani, michal.simek, nuno.sa, salih.erim
Cc: Guilherme Ivo Bozi, Jonathan.Cameron, linux-arm-kernel, linux-iio
In-Reply-To: <20260513003503.339418-1-guilherme.bozi@usp.br>
ams_event_to_channel() may return a pointer past the end of
dev->channels when no matching scan_index is found. This can lead
to invalid memory access in ams_handle_event().
Add a bounds check in ams_event_to_channel() and return NULL when
no channel is found. Also guard the caller to safely handle this
case.
Fixes: d5c70627a794 ("iio: adc: Add Xilinx AMS driver")
Signed-off-by: Guilherme Ivo Bozi <guilherme.bozi@usp.br>
---
drivers/iio/adc/xilinx-ams.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c
index 124470c92529..6191cd1b29a5 100644
--- a/drivers/iio/adc/xilinx-ams.c
+++ b/drivers/iio/adc/xilinx-ams.c
@@ -871,6 +871,9 @@ static const struct iio_chan_spec *ams_event_to_channel(struct iio_dev *dev,
if (dev->channels[i].scan_index == scan_index)
break;
+ if (i == dev->num_channels)
+ return NULL;
+
return &dev->channels[i];
}
@@ -1012,6 +1015,8 @@ static void ams_handle_event(struct iio_dev *indio_dev, u32 event)
const struct iio_chan_spec *chan;
chan = ams_event_to_channel(indio_dev, event);
+ if (!chan)
+ return;
if (chan->type == IIO_TEMP) {
/*
--
2.47.3
^ permalink raw reply related
* [PATCH v4 2/3] iio: adc: xilinx-ams: use guard(mutex) for automatic locking
From: Guilherme Ivo Bozi @ 2026-05-13 0:31 UTC (permalink / raw)
To: anand.ashok.dumbre, andy, conall.ogriofa, dlechner, jic23,
manish.narani, michal.simek, nuno.sa, salih.erim
Cc: Guilherme Ivo Bozi, Andy Shevchenko, Jonathan.Cameron,
linux-arm-kernel, linux-iio
In-Reply-To: <20260513003503.339418-1-guilherme.bozi@usp.br>
Replace open-coded mutex_lock()/mutex_unlock() pairs with
guard(mutex) to simplify locking and ensure proper unlock on
all control flow paths.
This removes explicit unlock handling, reduces boilerplate,
and avoids potential mistakes in error paths while keeping
the behavior unchanged.
Signed-off-by: Guilherme Ivo Bozi <guilherme.bozi@usp.br>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
---
drivers/iio/adc/xilinx-ams.c | 24 ++++++++----------------
1 file changed, 8 insertions(+), 16 deletions(-)
diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c
index 6191cd1b29a5..70a1f97f6dad 100644
--- a/drivers/iio/adc/xilinx-ams.c
+++ b/drivers/iio/adc/xilinx-ams.c
@@ -720,22 +720,20 @@ static int ams_read_raw(struct iio_dev *indio_dev,
int ret;
switch (mask) {
- case IIO_CHAN_INFO_RAW:
- mutex_lock(&ams->lock);
+ case IIO_CHAN_INFO_RAW: {
+ guard(mutex)(&ams->lock);
if (chan->scan_index >= AMS_CTRL_SEQ_BASE) {
ret = ams_read_vcc_reg(ams, chan->address, val);
if (ret)
- goto unlock_mutex;
+ return ret;
ams_enable_channel_sequence(indio_dev);
} else if (chan->scan_index >= AMS_PS_SEQ_MAX)
*val = readl(ams->pl_base + chan->address);
else
*val = readl(ams->ps_base + chan->address);
- ret = IIO_VAL_INT;
-unlock_mutex:
- mutex_unlock(&ams->lock);
- return ret;
+ return IIO_VAL_INT;
+ }
case IIO_CHAN_INFO_SCALE:
switch (chan->type) {
case IIO_VOLTAGE:
@@ -939,7 +937,7 @@ static int ams_write_event_config(struct iio_dev *indio_dev,
alarm = ams_get_alarm_mask(chan->scan_index);
- mutex_lock(&ams->lock);
+ guard(mutex)(&ams->lock);
if (state)
ams->alarm_mask |= alarm;
@@ -948,8 +946,6 @@ static int ams_write_event_config(struct iio_dev *indio_dev,
ams_update_alarm(ams, ams->alarm_mask);
- mutex_unlock(&ams->lock);
-
return 0;
}
@@ -962,15 +958,13 @@ static int ams_read_event_value(struct iio_dev *indio_dev,
struct ams *ams = iio_priv(indio_dev);
unsigned int offset = ams_get_alarm_offset(chan->scan_index, dir);
- mutex_lock(&ams->lock);
+ guard(mutex)(&ams->lock);
if (chan->scan_index >= AMS_PS_SEQ_MAX)
*val = readl(ams->pl_base + offset);
else
*val = readl(ams->ps_base + offset);
- mutex_unlock(&ams->lock);
-
return IIO_VAL_INT;
}
@@ -983,7 +977,7 @@ static int ams_write_event_value(struct iio_dev *indio_dev,
struct ams *ams = iio_priv(indio_dev);
unsigned int offset;
- mutex_lock(&ams->lock);
+ guard(mutex)(&ams->lock);
/* Set temperature channel threshold to direct threshold */
if (chan->type == IIO_TEMP) {
@@ -1005,8 +999,6 @@ static int ams_write_event_value(struct iio_dev *indio_dev,
else
writel(val, ams->ps_base + offset);
- mutex_unlock(&ams->lock);
-
return 0;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v4 3/3] iio: adc: xilinx-ams: refactor alarm mapping to table-driven approach
From: Guilherme Ivo Bozi @ 2026-05-13 0:31 UTC (permalink / raw)
To: anand.ashok.dumbre, andy, conall.ogriofa, dlechner, jic23,
manish.narani, michal.simek, nuno.sa, salih.erim
Cc: Guilherme Ivo Bozi, Jonathan.Cameron, linux-arm-kernel, linux-iio
In-Reply-To: <20260513003503.339418-1-guilherme.bozi@usp.br>
Replace multiple open-coded switch statements that map between
scan_index, alarm bits, and register offsets with a centralized
table-driven approach.
Introduce a struct-based alarm_map to describe the relationship
between scan indices and alarm offsets, and add a helper to
translate scan_index to event IDs. This removes duplicated logic
across ams_get_alarm_offset(), ams_event_to_channel(), and
ams_get_alarm_mask().
The new approach improves maintainability, reduces code size,
and makes it easier to extend or modify alarm mappings in the
future, while preserving existing behavior.
Signed-off-by: Guilherme Ivo Bozi <guilherme.bozi@usp.br>
---
drivers/iio/adc/xilinx-ams.c | 161 +++++++++++++----------------------
1 file changed, 58 insertions(+), 103 deletions(-)
diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c
index 70a1f97f6dad..d38c4401dfce 100644
--- a/drivers/iio/adc/xilinx-ams.c
+++ b/drivers/iio/adc/xilinx-ams.c
@@ -102,6 +102,7 @@
#define AMS_PS_SEQ_MASK GENMASK(21, 0)
#define AMS_PL_SEQ_MASK GENMASK_ULL(59, 22)
+#define AMS_ALARM_NONE 0x000 /* not a real offset */
#define AMS_ALARM_TEMP 0x140
#define AMS_ALARM_SUPPLY1 0x144
#define AMS_ALARM_SUPPLY2 0x148
@@ -763,9 +764,49 @@ static int ams_read_raw(struct iio_dev *indio_dev,
}
}
+struct ams_alarm_map {
+ enum ams_ps_pl_seq scan_index;
+ unsigned int base_offset;
+};
+
+/*
+ * Array index matches enum ams_alarm_bit.
+ * Entries with base_offset == AMS_ALARM_NONE are unused/invalid
+ * (e.g. RESERVED) and must be skipped.
+ */
+static const struct ams_alarm_map alarm_map[] = {
+ [AMS_ALARM_BIT_TEMP] = { AMS_SEQ_TEMP, AMS_ALARM_TEMP },
+ [AMS_ALARM_BIT_SUPPLY1] = { AMS_SEQ_SUPPLY1, AMS_ALARM_SUPPLY1 },
+ [AMS_ALARM_BIT_SUPPLY2] = { AMS_SEQ_SUPPLY2, AMS_ALARM_SUPPLY2 },
+ [AMS_ALARM_BIT_SUPPLY3] = { AMS_SEQ_SUPPLY3, AMS_ALARM_SUPPLY3 },
+ [AMS_ALARM_BIT_SUPPLY4] = { AMS_SEQ_SUPPLY4, AMS_ALARM_SUPPLY4 },
+ [AMS_ALARM_BIT_SUPPLY5] = { AMS_SEQ_SUPPLY5, AMS_ALARM_SUPPLY5 },
+ [AMS_ALARM_BIT_SUPPLY6] = { AMS_SEQ_SUPPLY6, AMS_ALARM_SUPPLY6 },
+ [AMS_ALARM_BIT_RESERVED] = { 0, AMS_ALARM_NONE },
+ [AMS_ALARM_BIT_SUPPLY7] = { AMS_SEQ_SUPPLY7, AMS_ALARM_SUPPLY7 },
+ [AMS_ALARM_BIT_SUPPLY8] = { AMS_SEQ_SUPPLY8, AMS_ALARM_SUPPLY8 },
+ [AMS_ALARM_BIT_SUPPLY9] = { AMS_SEQ_SUPPLY9, AMS_ALARM_SUPPLY9 },
+ [AMS_ALARM_BIT_SUPPLY10] = { AMS_SEQ_SUPPLY10, AMS_ALARM_SUPPLY10 },
+ [AMS_ALARM_BIT_VCCAMS] = { AMS_SEQ_VCCAMS, AMS_ALARM_VCCAMS },
+ [AMS_ALARM_BIT_TEMP_REMOTE] = { AMS_SEQ_TEMP_REMOTE, AMS_ALARM_TEMP_REMOTE },
+};
+
+static int ams_scan_index_to_event(int scan_index)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(alarm_map); i++) {
+ if (alarm_map[i].base_offset == AMS_ALARM_NONE)
+ continue;
+
+ if (alarm_map[i].scan_index == scan_index)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
static int ams_get_alarm_offset(int scan_index, enum iio_event_direction dir)
{
- int offset;
+ int offset, event;
if (scan_index >= AMS_PS_SEQ_MAX)
scan_index -= AMS_PS_SEQ_MAX;
@@ -779,36 +820,11 @@ static int ams_get_alarm_offset(int scan_index, enum iio_event_direction dir)
offset = 0;
}
- switch (scan_index) {
- case AMS_SEQ_TEMP:
- return AMS_ALARM_TEMP + offset;
- case AMS_SEQ_SUPPLY1:
- return AMS_ALARM_SUPPLY1 + offset;
- case AMS_SEQ_SUPPLY2:
- return AMS_ALARM_SUPPLY2 + offset;
- case AMS_SEQ_SUPPLY3:
- return AMS_ALARM_SUPPLY3 + offset;
- case AMS_SEQ_SUPPLY4:
- return AMS_ALARM_SUPPLY4 + offset;
- case AMS_SEQ_SUPPLY5:
- return AMS_ALARM_SUPPLY5 + offset;
- case AMS_SEQ_SUPPLY6:
- return AMS_ALARM_SUPPLY6 + offset;
- case AMS_SEQ_SUPPLY7:
- return AMS_ALARM_SUPPLY7 + offset;
- case AMS_SEQ_SUPPLY8:
- return AMS_ALARM_SUPPLY8 + offset;
- case AMS_SEQ_SUPPLY9:
- return AMS_ALARM_SUPPLY9 + offset;
- case AMS_SEQ_SUPPLY10:
- return AMS_ALARM_SUPPLY10 + offset;
- case AMS_SEQ_VCCAMS:
- return AMS_ALARM_VCCAMS + offset;
- case AMS_SEQ_TEMP_REMOTE:
- return AMS_ALARM_TEMP_REMOTE + offset;
- default:
+ event = ams_scan_index_to_event(scan_index);
+ if (event < 0 || alarm_map[event].base_offset == AMS_ALARM_NONE)
return 0;
- }
+
+ return alarm_map[event].base_offset + offset;
}
static const struct iio_chan_spec *ams_event_to_channel(struct iio_dev *dev,
@@ -821,49 +837,13 @@ static const struct iio_chan_spec *ams_event_to_channel(struct iio_dev *dev,
scan_index = AMS_PS_SEQ_MAX;
}
- switch (event) {
- case AMS_ALARM_BIT_TEMP:
- scan_index += AMS_SEQ_TEMP;
- break;
- case AMS_ALARM_BIT_SUPPLY1:
- scan_index += AMS_SEQ_SUPPLY1;
- break;
- case AMS_ALARM_BIT_SUPPLY2:
- scan_index += AMS_SEQ_SUPPLY2;
- break;
- case AMS_ALARM_BIT_SUPPLY3:
- scan_index += AMS_SEQ_SUPPLY3;
- break;
- case AMS_ALARM_BIT_SUPPLY4:
- scan_index += AMS_SEQ_SUPPLY4;
- break;
- case AMS_ALARM_BIT_SUPPLY5:
- scan_index += AMS_SEQ_SUPPLY5;
- break;
- case AMS_ALARM_BIT_SUPPLY6:
- scan_index += AMS_SEQ_SUPPLY6;
- break;
- case AMS_ALARM_BIT_SUPPLY7:
- scan_index += AMS_SEQ_SUPPLY7;
- break;
- case AMS_ALARM_BIT_SUPPLY8:
- scan_index += AMS_SEQ_SUPPLY8;
- break;
- case AMS_ALARM_BIT_SUPPLY9:
- scan_index += AMS_SEQ_SUPPLY9;
- break;
- case AMS_ALARM_BIT_SUPPLY10:
- scan_index += AMS_SEQ_SUPPLY10;
- break;
- case AMS_ALARM_BIT_VCCAMS:
- scan_index += AMS_SEQ_VCCAMS;
- break;
- case AMS_ALARM_BIT_TEMP_REMOTE:
- scan_index += AMS_SEQ_TEMP_REMOTE;
- break;
- default:
- break;
- }
+ if (event >= ARRAY_SIZE(alarm_map))
+ return NULL;
+
+ if (alarm_map[event].base_offset == AMS_ALARM_NONE)
+ return NULL;
+
+ scan_index += alarm_map[event].scan_index;
for (i = 0; i < dev->num_channels; i++)
if (dev->channels[i].scan_index == scan_index)
@@ -877,43 +857,18 @@ static const struct iio_chan_spec *ams_event_to_channel(struct iio_dev *dev,
static int ams_get_alarm_mask(int scan_index)
{
- int bit = 0;
+ int bit = 0, event;
if (scan_index >= AMS_PS_SEQ_MAX) {
bit = AMS_PL_ALARM_START;
scan_index -= AMS_PS_SEQ_MAX;
}
- switch (scan_index) {
- case AMS_SEQ_TEMP:
- return BIT(AMS_ALARM_BIT_TEMP + bit);
- case AMS_SEQ_SUPPLY1:
- return BIT(AMS_ALARM_BIT_SUPPLY1 + bit);
- case AMS_SEQ_SUPPLY2:
- return BIT(AMS_ALARM_BIT_SUPPLY2 + bit);
- case AMS_SEQ_SUPPLY3:
- return BIT(AMS_ALARM_BIT_SUPPLY3 + bit);
- case AMS_SEQ_SUPPLY4:
- return BIT(AMS_ALARM_BIT_SUPPLY4 + bit);
- case AMS_SEQ_SUPPLY5:
- return BIT(AMS_ALARM_BIT_SUPPLY5 + bit);
- case AMS_SEQ_SUPPLY6:
- return BIT(AMS_ALARM_BIT_SUPPLY6 + bit);
- case AMS_SEQ_SUPPLY7:
- return BIT(AMS_ALARM_BIT_SUPPLY7 + bit);
- case AMS_SEQ_SUPPLY8:
- return BIT(AMS_ALARM_BIT_SUPPLY8 + bit);
- case AMS_SEQ_SUPPLY9:
- return BIT(AMS_ALARM_BIT_SUPPLY9 + bit);
- case AMS_SEQ_SUPPLY10:
- return BIT(AMS_ALARM_BIT_SUPPLY10 + bit);
- case AMS_SEQ_VCCAMS:
- return BIT(AMS_ALARM_BIT_VCCAMS + bit);
- case AMS_SEQ_TEMP_REMOTE:
- return BIT(AMS_ALARM_BIT_TEMP_REMOTE + bit);
- default:
+ event = ams_scan_index_to_event(scan_index);
+ if (event < 0)
return 0;
- }
+
+ return BIT(event + bit);
}
static int ams_read_event_config(struct iio_dev *indio_dev,
--
2.47.3
^ permalink raw reply related
* [PATCH] arm64: mm: fix accidental linear mapping of no-map reserved memory
From: liulhong617 @ 2026-05-13 1:02 UTC (permalink / raw)
To: catalin.marinas, will; +Cc: linux-arm-kernel, linux-kernel, liulhong617
From: liulhong617 <liulhong617@gmail.com>
When reserved-memory regions with the "no-map" property are not
page-aligned, the kernel may accidentally map them into the linear
mapping, contradicting the no-map semantics.
The root cause is a mismatch between /proc/iomem's address boundaries
and the actual page table mapping boundaries:
1. /proc/iomem derives its ranges from memblock via
memblock_region_reserved_base_pfn/memblock_region_reserved_end_pfn,
which perform PFN rounding so the displayed boundaries are
page-aligned. This gives the impression that the no-map region
occupies whole pages.
2. However, memblock_mark_nomap() splits memblock.memory regions at
exact byte boundaries (memblock_isolate_range preserves raw DT
base/size with no alignment). When for_each_mem_range iterates the
non-NOMAP regions adjacent to a no-map region, it returns start/end
values that are NOT page-aligned — they are the precise byte
boundaries from the memblock split.
3. These sub-page-aligned values are passed to
__create_pgd_mapping_locked(), which does:
phys &= PAGE_MASK;
addr = virt & PAGE_MASK;
end = PAGE_ALIGN(virt + size);
The downward rounding of phys via PAGE_MASK extends the mapped
range backward into the adjacent no-map region, effectively
including no-map memory in the linear mapping.
For example, with 64K pages, reserved_region@A2000000 (base=0xA2000000,
size=0x8000, no-map) causes for_each_mem_range to return
start=0xA2008000 for the next mappable region. After phys &= PAGE_MASK,
the actual mapping starts at 0xA2000000 — the entire no-map region is
incorrectly mapped.
Fix this by rounding the mappable range inward to PAGE_SIZE boundaries
before passing it to __map_memblock: start is rounded UP and end is
rounded DOWN. This ensures the mapped area never overlaps with adjacent
no-map regions. The cost is at most one page of unmapped gap at each
boundary, which is preferable to violating no-map semantics.
Signed-off-by: liulhong617 <liulhong617@gmail.com>
---
arch/arm64/mm/mmu.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index dd85e093f..bc8ac7622 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -1175,6 +1175,20 @@ static void __init map_mem(pgd_t *pgdp)
for_each_mem_range(i, &start, &end) {
if (start >= end)
break;
+ /*
+ * for_each_mem_range may return sub-page-aligned boundaries
+ * after memblock_mark_nomap() splits regions at byte precision.
+ * __create_pgd_mapping_locked aligns phys down to PAGE_MASK,
+ * which could accidentally map no-map memory on the boundary.
+ * Round the mappable range inward: start UP, end DOWN, so
+ * that the mapped area never overlaps with adjacent no-map
+ * regions. The cost is at most one page of unmapped gap at
+ * each boundary.
+ */
+ start = PAGE_ALIGN(start);
+ end = end & PAGE_MASK;
+ if (start >= end)
+ continue;
/*
* The linear map must allow allocation tags reading/writing
* if MTE is present. Otherwise, it has the same attributes as
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox