[U-Boot] [PATCH v3 5/6] drivers/misc: Add basic support for ATSHA204A Crypto module

Marek Behun marek.behun at nic.cz
Tue Jun 6 12:04:36 UTC 2017


From: Marek BehĂșn <marek.behun at nic.cz>

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 at cryptotronix.com
and also
  Tomas Hlavacek, CZ.NIC, tomas.hlavacek at nic.cz

Signed-off-by: Tomas Hlavacek <tomas.hlavacek at nic.cz>
Signed-off-by: Marek Behun <marek.behun at nic.cz>

 create mode 100644 drivers/misc/atsha204a-i2c.c
 create mode 100644 include/atsha204a-i2c.h

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index ecca159d14..8073cdc628 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -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 CMD_CROS_EC
 	bool "Enable crosec command"
 	depends on CROS_EC
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 4543cd647e..bb51f9fafe 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -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
index 0000000000..f9fff06072
--- /dev/null
+++ b/drivers/misc/atsha204a-i2c.c
@@ -0,0 +1,307 @@
+/*
+ * I2C Driver for Atmel ATSHA204 over I2C
+ *
+ * Copyright (C) 2014 Josh Datko, Cryptotronix, jbd at cryptotronix.com
+ * 		 2016 Tomas Hlavacek, CZ.NIC, tmshlvck at gmail.com
+ * 		 2017 Marek Behun, CZ.NIC, marek.behun at 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;
+
+static u16 atsha204a_crc16(const u8 *buf, u8 len)
+{
+	u8 i;
+	u16 crc16 = 0;
+	u8 shift;
+
+	for (i = 0; i < len; i++) {
+		for (shift = 0x01; shift > 0x00; shift <<= 1) {
+			u8 data_bit = (buf[i] & shift) ? 1 : 0;
+			u8 crc_bit = crc16 >> 15;
+
+			crc16 <<= 1;
+
+			if ((data_bit ^ crc_bit) != 0)
+				crc16 ^= 0x8005;
+		}
+	}
+
+	return cpu_to_le16(crc16);
+}
+
+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
index 0000000000..344fd8ace7
--- /dev/null
+++ b/include/atsha204a-i2c.h
@@ -0,0 +1,69 @@
+/*
+ * I2C Driver for Atmel ATSHA204 over I2C
+ *
+ * Copyright (C) 2014 Josh Datko, Cryptotronix, jbd at cryptotronix.com
+ * 		 2016 Tomas Hlavacek, CZ.NIC, tmshlvck at gmail.com
+ * 		 2017 Marek Behun, CZ.NIC, marek.behun at 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_ */
-- 
2.13.0



More information about the U-Boot mailing list