drivers/misc: Add basic support for ATSHA204A Crypto module
authorMarek BehĂșn <marek.behun@nic.cz>
Fri, 9 Jun 2017 17:28:44 +0000 (19:28 +0200)
committerStefan Roese <sr@denx.de>
Wed, 12 Jul 2017 04:57:38 +0000 (06:57 +0200)
This module can be found on the Turris Omnia board connected
via the I2C interface.

Among some cryptographic functions, the chip has a 512 bit
One Time Programmable memory, 88 byte configuration memory
and 512 byte general purpose memory.

The Turris Omnia stores serial number and device MAC address in
the OTP memory.

This commit adds basic support for reading the EEPROM and also
exposes the chips Random Number Generator.

The driver is based on code by
  Josh Datko, Cryptotronix, jbd@cryptotronix.com
and also
  Tomas Hlavacek, CZ.NIC, tomas.hlavacek@nic.cz

Signed-off-by: Tomas Hlavacek <tomas.hlavacek@nic.cz>
Signed-off-by: Marek Behun <marek.behun@nic.cz>
 create mode 100644 drivers/misc/atsha204a-i2c.c
 create mode 100644 include/atsha204a-i2c.h
Signed-off-by: Stefan Roese <sr@denx.de>
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/atsha204a-i2c.c [new file with mode: 0644]
include/atsha204a-i2c.h [new file with mode: 0644]

index 92f348f409089fdd77e61edc691278b15f7ed1ee..d1ddbbe157cffc062575dd543cd5c4cb26e8d69c 100644 (file)
@@ -20,6 +20,14 @@ config ALTERA_SYSID
          Select this to enable a sysid for Altera devices. Please find
          details on the "Embedded Peripherals IP User Guide" of Altera.
 
+config ATSHA204A
+       bool "Support for Atmel ATSHA204A module"
+       depends on MISC
+       help
+          Enable support for I2C connected Atmel's ATSHA204A
+          CryptoAuthentication module found for example on the Turris Omnia
+          board.
+
 config ROCKCHIP_EFUSE
         bool "Rockchip e-fuse support"
        depends on MISC
index ea64677c3339f09d8cb8dfb43162c5a2a3a4d941..10265c8fb4693f4fb454f8c0fd77a6150586cc8c 100644 (file)
@@ -8,6 +8,7 @@
 obj-$(CONFIG_MISC) += misc-uclass.o
 obj-$(CONFIG_ALI152X) += ali512x.o
 obj-$(CONFIG_ALTERA_SYSID) += altera_sysid.o
+obj-$(CONFIG_ATSHA204A) += atsha204a-i2c.o
 obj-$(CONFIG_DS4510)  += ds4510.o
 obj-$(CONFIG_CBMEM_CONSOLE) += cbmem_console.o
 ifndef CONFIG_SPL_BUILD
diff --git a/drivers/misc/atsha204a-i2c.c b/drivers/misc/atsha204a-i2c.c
new file mode 100644 (file)
index 0000000..934ba5e
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * I2C Driver for Atmel ATSHA204 over I2C
+ *
+ * Copyright (C) 2014 Josh Datko, Cryptotronix, jbd@cryptotronix.com
+ *              2016 Tomas Hlavacek, CZ.NIC, tmshlvck@gmail.com
+ *              2017 Marek Behun, CZ.NIC, marek.behun@nic.cz
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <i2c.h>
+#include <errno.h>
+#include <atsha204a-i2c.h>
+
+#define ATSHA204A_TWLO                 60
+#define ATSHA204A_TRANSACTION_TIMEOUT  100000
+#define ATSHA204A_TRANSACTION_RETRY    5
+#define ATSHA204A_EXECTIME             5000
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/*
+ * The ATSHA204A uses an (to me) unknown CRC-16 algorithm.
+ * The Reveng CRC-16 catalogue does not contain it.
+ *
+ * Because in Atmel's documentation only a primitive implementation
+ * can be found, I have implemented this one with lookup table.
+ */
+
+/*
+ * This is the code that computes the table below:
+ *
+ * int i, j;
+ * for (i = 0; i < 256; ++i) {
+ *     u8 c = 0;
+ *     for (j = 0; j < 8; ++j) {
+ *             c = (c << 1) | ((i >> j) & 1);
+ *     }
+ *     bitreverse_table[i] = c;
+ * }
+ */
+
+static u8 const bitreverse_table[256] = {
+       0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+       0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+       0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+       0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+       0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+       0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+       0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+       0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+       0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+       0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+       0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+       0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+       0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+       0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+       0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+       0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+       0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+       0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+       0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+       0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+       0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+       0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+       0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+       0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+       0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+       0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+       0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+       0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+       0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+       0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+       0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+       0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+/*
+ * This is the code that computes the table below:
+ *
+ * int i, j;
+ * for (i = 0; i < 256; ++i) {
+ *     u16 c = i << 8;
+ *     for (j = 0; j < 8; ++j) {
+ *             int b = c >> 15;
+ *             c <<= 1;
+ *             if (b)
+ *                     c ^= 0x8005;
+ *     }
+ *     crc16_table[i] = c;
+ * }
+ */
+static u16 const crc16_table[256] = {
+       0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011,
+       0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022,
+       0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072,
+       0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041,
+       0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2,
+       0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1,
+       0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1,
+       0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082,
+       0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192,
+       0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1,
+       0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1,
+       0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2,
+       0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151,
+       0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162,
+       0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132,
+       0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101,
+       0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312,
+       0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321,
+       0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371,
+       0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342,
+       0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1,
+       0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2,
+       0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2,
+       0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381,
+       0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291,
+       0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2,
+       0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2,
+       0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1,
+       0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252,
+       0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261,
+       0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231,
+       0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202,
+};
+
+static inline u16 crc16_byte(u16 crc, const u8 data)
+{
+       u16 t = crc16_table[((crc >> 8) ^ bitreverse_table[data]) & 0xff];
+       return ((crc << 8) ^ t);
+}
+
+static u16 atsha204a_crc16(const u8 *buffer, size_t len)
+{
+       u16 crc = 0;
+
+       while (len--)
+               crc = crc16_byte(crc, *buffer++);
+
+       return cpu_to_le16(crc);
+}
+
+static int atsha204a_send(struct udevice *dev, const u8 *buf, u8 len)
+{
+       fdt_addr_t *priv = dev_get_priv(dev);
+       struct i2c_msg msg;
+
+       msg.addr = *priv;
+       msg.flags = I2C_M_STOP;
+       msg.len = len;
+       msg.buf = (u8 *) buf;
+
+       return dm_i2c_xfer(dev, &msg, 1);
+}
+
+static int atsha204a_recv(struct udevice *dev, u8 *buf, u8 len)
+{
+       fdt_addr_t *priv = dev_get_priv(dev);
+       struct i2c_msg msg;
+
+       msg.addr = *priv;
+       msg.flags = I2C_M_RD | I2C_M_STOP;
+       msg.len = len;
+       msg.buf = (u8 *) buf;
+
+       return dm_i2c_xfer(dev, &msg, 1);
+}
+
+static int atsha204a_recv_resp(struct udevice *dev,
+                              struct atsha204a_resp *resp)
+{
+       int res;
+       u16 resp_crc, computed_crc;
+       u8 *p = (u8 *) resp;
+
+       res = atsha204a_recv(dev, p, 4);
+       if (res)
+               return res;
+
+       if (resp->length > 4) {
+               if (resp->length > sizeof(*resp))
+                       return -EMSGSIZE;
+
+               res = atsha204a_recv(dev, p + 4, resp->length - 4);
+               if (res)
+                       return res;
+       }
+
+       resp_crc = (u16) p[resp->length - 2]
+                  | (((u16) p[resp->length - 1]) << 8);
+       computed_crc = atsha204a_crc16(p, resp->length - 2);
+
+       if (resp_crc != computed_crc) {
+               debug("Invalid checksum in ATSHA204A response\n");
+               return -EBADMSG;
+       }
+
+       return 0;
+}
+
+int atsha204a_wakeup(struct udevice *dev)
+{
+       u8 req[4];
+       struct atsha204a_resp resp;
+       int try, res;
+
+       debug("Waking up ATSHA204A\n");
+
+       for (try = 1; try <= 10; ++try) {
+               debug("Try %i... ", try);
+
+               memset(req, 0, 4);
+               res = atsha204a_send(dev, req, 4);
+               if (res) {
+                       debug("failed on I2C send, trying again\n");
+                       continue;
+               }
+
+               udelay(ATSHA204A_TWLO);
+
+               res = atsha204a_recv_resp(dev, &resp);
+               if (res) {
+                       debug("failed on receiving response, ending\n");
+                       return res;
+               }
+
+               if (resp.code != ATSHA204A_STATUS_AFTER_WAKE) {
+                       debug ("failed (responce code = %02x), ending\n",
+                              resp.code);
+                       return -EBADMSG;
+               }
+
+               debug("success\n");
+               break;
+       }
+
+       return 0;
+}
+
+int atsha204a_idle(struct udevice *dev)
+{
+       int res;
+       u8 req = ATSHA204A_FUNC_IDLE;
+
+       res = atsha204a_send(dev, &req, 1);
+       if (res)
+               debug("Failed putting ATSHA204A idle\n");
+       return res;
+}
+
+int atsha204a_sleep(struct udevice *dev)
+{
+       int res;
+       u8 req = ATSHA204A_FUNC_IDLE;
+
+       res = atsha204a_send(dev, &req, 1);
+       if (res)
+               debug("Failed putting ATSHA204A to sleep\n");
+       return res;
+}
+
+static int atsha204a_transaction(struct udevice *dev, struct atsha204a_req *req,
+                               struct atsha204a_resp *resp)
+{
+       int res, timeout = ATSHA204A_TRANSACTION_TIMEOUT;
+
+       res = atsha204a_send(dev, (u8 *) req, req->length + 1);
+       if (res) {
+               debug("ATSHA204A transaction send failed\n");
+               return -EBUSY;
+       }
+
+       do {
+               res = atsha204a_recv_resp(dev, resp);
+               if (!res || res == -EMSGSIZE || res == -EBADMSG)
+                       break;
+
+               debug("ATSHA204A transaction polling for response "
+                     "(timeout = %d)\n", timeout);
+
+               udelay(ATSHA204A_EXECTIME);
+               timeout -= ATSHA204A_EXECTIME;
+       } while (timeout > 0);
+
+       if (timeout <= 0) {
+               debug("ATSHA204A transaction timed out\n");
+               return -ETIMEDOUT;
+       }
+
+       return res;
+}
+
+static void atsha204a_req_crc32(struct atsha204a_req *req)
+{
+       u8 *p = (u8 *) req;
+       u16 computed_crc;
+       u16 *crc_ptr = (u16 *) &p[req->length - 1];
+
+       /* The buffer to crc16 starts at byte 1, not 0 */
+       computed_crc = atsha204a_crc16(p + 1, req->length - 2);
+
+       *crc_ptr = cpu_to_le16(computed_crc);
+}
+
+int atsha204a_read(struct udevice *dev, enum atsha204a_zone zone, bool read32,
+                 u16 addr, u8 *buffer)
+{
+       int res, retry = ATSHA204A_TRANSACTION_RETRY;
+       struct atsha204a_req req;
+       struct atsha204a_resp resp;
+
+       req.function = ATSHA204A_FUNC_COMMAND;
+       req.length = 7;
+       req.command = ATSHA204A_CMD_READ;
+
+       req.param1 = (u8) zone;
+       if (read32)
+               req.param1 |= 0x80;
+
+       req.param2 = cpu_to_le16(addr);
+
+       atsha204a_req_crc32(&req);
+
+       do {
+               res = atsha204a_transaction(dev, &req, &resp);
+               if (!res)
+                       break;
+
+               debug("ATSHA204A read retry (%d)\n", retry);
+               retry--;
+               atsha204a_wakeup(dev);
+       } while (retry >= 0);
+       
+       if (res) {
+               debug("ATSHA204A read failed\n");
+               return res;
+       }
+
+       if (resp.length != (read32 ? 32 : 4) + 3) {
+               debug("ATSHA204A read bad response length (%d)\n",
+                     resp.length);
+               return -EBADMSG;
+       }
+
+       memcpy(buffer, ((u8 *) &resp) + 1, read32 ? 32 : 4);
+
+       return 0;
+}
+
+int atsha204a_get_random(struct udevice *dev, u8 *buffer, size_t max)
+{
+       int res;
+       struct atsha204a_req req;
+       struct atsha204a_resp resp;
+
+       req.function = ATSHA204A_FUNC_COMMAND;
+       req.length = 7;
+       req.command = ATSHA204A_CMD_RANDOM;
+
+       req.param1 = 1;
+       req.param2 = 0;
+
+       /* We do not have to compute the checksum dynamically */
+       req.data[0] = 0x27;
+       req.data[1] = 0x47;
+
+       res = atsha204a_transaction(dev, &req, &resp);
+       if (res) {
+               debug("ATSHA204A random transaction failed\n");
+               return res;
+       }
+
+       memcpy(buffer, ((u8 *) &resp) + 1, max >= 32 ? 32 : max);
+       return 0;
+}
+
+static int atsha204a_ofdata_to_platdata(struct udevice *dev)
+{
+       fdt_addr_t *priv = dev_get_priv(dev);
+       fdt_addr_t addr;
+
+       addr = fdtdec_get_addr(gd->fdt_blob, dev_of_offset(dev), "reg");
+       if (addr == FDT_ADDR_T_NONE) {
+               debug("Can't get ATSHA204A I2C base address\n");
+               return -ENXIO;
+       }
+
+       *priv = addr;
+       return 0;
+}
+
+static const struct udevice_id atsha204a_ids[] = {
+       { .compatible = "atmel,atsha204a" },
+       { }
+};
+
+U_BOOT_DRIVER(atsha204) = {
+       .name                   = "atsha204",
+       .id                     = UCLASS_MISC,
+       .of_match               = atsha204a_ids,
+       .ofdata_to_platdata     = atsha204a_ofdata_to_platdata,
+       .priv_auto_alloc_size   = sizeof(fdt_addr_t),
+};
diff --git a/include/atsha204a-i2c.h b/include/atsha204a-i2c.h
new file mode 100644 (file)
index 0000000..344fd8a
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * I2C Driver for Atmel ATSHA204 over I2C
+ *
+ * Copyright (C) 2014 Josh Datko, Cryptotronix, jbd@cryptotronix.com
+ *              2016 Tomas Hlavacek, CZ.NIC, tmshlvck@gmail.com
+ *              2017 Marek Behun, CZ.NIC, marek.behun@nic.cz
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _ATSHA204_I2C_H_
+#define _ATSHA204_I2C_H_
+
+enum atsha204a_zone
+{
+       ATSHA204A_ZONE_CONFIG   = 0,
+       ATSHA204A_ZONE_OTP      = 1,
+       ATSHA204A_ZONE_DATA     = 2,
+};
+
+enum atsha204a_status
+{
+       ATSHA204A_STATUS_SUCCESS        = 0x00,
+       ATSHA204A_STATUS_MISCOMPARE     = 0x01,
+       ATSHA204A_STATUS_PARSE_ERROR    = 0x03,
+       ATSHA204A_STATUS_EXEC_ERROR     = 0x0F,
+       ATSHA204A_STATUS_AFTER_WAKE     = 0x11,
+       ATSHA204A_STATUS_CRC_ERROR      = 0xFF,
+};
+
+enum atsha204a_func
+{
+       ATSHA204A_FUNC_RESET    = 0x00,
+       ATSHA204A_FUNC_SLEEP    = 0x01,
+       ATSHA204A_FUNC_IDLE     = 0x02,
+       ATSHA204A_FUNC_COMMAND  = 0x03,
+};
+
+enum atsha204a_cmd
+{
+       ATSHA204A_CMD_READ      = 0x02,
+       ATSHA204A_CMD_RANDOM    = 0x1B,
+};
+
+struct atsha204a_resp
+{
+       u8 length;
+       u8 code;
+       u8 data[82];
+} __attribute__ ((packed));
+
+struct atsha204a_req
+{
+       u8 function;
+       u8 length;
+       u8 command;
+       u8 param1;
+       u16 param2;
+       u8 data[78];
+} __attribute__ ((packed));
+
+int atsha204a_wakeup(struct udevice *);
+int atsha204a_idle(struct udevice *);
+int atsha204a_sleep(struct udevice *);
+int atsha204a_read(struct udevice *, enum atsha204a_zone, bool, u16, u8 *);
+int atsha204a_get_random(struct udevice *, u8 *, size_t);
+
+#endif /* _ATSHA204_I2C_H_ */