cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ee1004.c (6268B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * ee1004 - driver for DDR4 SPD EEPROMs
      4 *
      5 * Copyright (C) 2017-2019 Jean Delvare
      6 *
      7 * Based on the at24 driver:
      8 * Copyright (C) 2005-2007 David Brownell
      9 * Copyright (C) 2008 Wolfram Sang, Pengutronix
     10 */
     11
     12#include <linux/i2c.h>
     13#include <linux/init.h>
     14#include <linux/kernel.h>
     15#include <linux/mod_devicetable.h>
     16#include <linux/module.h>
     17#include <linux/mutex.h>
     18
     19/*
     20 * DDR4 memory modules use special EEPROMs following the Jedec EE1004
     21 * specification. These are 512-byte EEPROMs using a single I2C address
     22 * in the 0x50-0x57 range for data. One of two 256-byte page is selected
     23 * by writing a command to I2C address 0x36 or 0x37 on the same I2C bus.
     24 *
     25 * Therefore we need to request these 2 additional addresses, and serialize
     26 * access to all such EEPROMs with a single mutex.
     27 *
     28 * We assume it is safe to read up to 32 bytes at once from these EEPROMs.
     29 * We use SMBus access even if I2C is available, these EEPROMs are small
     30 * enough, and reading from them infrequent enough, that we favor simplicity
     31 * over performance.
     32 */
     33
     34#define EE1004_ADDR_SET_PAGE		0x36
     35#define EE1004_NUM_PAGES		2
     36#define EE1004_PAGE_SIZE		256
     37#define EE1004_PAGE_SHIFT		8
     38#define EE1004_EEPROM_SIZE		(EE1004_PAGE_SIZE * EE1004_NUM_PAGES)
     39
     40/*
     41 * Mutex protects ee1004_set_page and ee1004_dev_count, and must be held
     42 * from page selection to end of read.
     43 */
     44static DEFINE_MUTEX(ee1004_bus_lock);
     45static struct i2c_client *ee1004_set_page[EE1004_NUM_PAGES];
     46static unsigned int ee1004_dev_count;
     47static int ee1004_current_page;
     48
     49static const struct i2c_device_id ee1004_ids[] = {
     50	{ "ee1004", 0 },
     51	{ }
     52};
     53MODULE_DEVICE_TABLE(i2c, ee1004_ids);
     54
     55/*-------------------------------------------------------------------------*/
     56
     57static int ee1004_get_current_page(void)
     58{
     59	int err;
     60
     61	err = i2c_smbus_read_byte(ee1004_set_page[0]);
     62	if (err == -ENXIO) {
     63		/* Nack means page 1 is selected */
     64		return 1;
     65	}
     66	if (err < 0) {
     67		/* Anything else is a real error, bail out */
     68		return err;
     69	}
     70
     71	/* Ack means page 0 is selected, returned value meaningless */
     72	return 0;
     73}
     74
     75static int ee1004_set_current_page(struct device *dev, int page)
     76{
     77	int ret;
     78
     79	if (page == ee1004_current_page)
     80		return 0;
     81
     82	/* Data is ignored */
     83	ret = i2c_smbus_write_byte(ee1004_set_page[page], 0x00);
     84	/*
     85	 * Don't give up just yet. Some memory modules will select the page
     86	 * but not ack the command. Check which page is selected now.
     87	 */
     88	if (ret == -ENXIO && ee1004_get_current_page() == page)
     89		ret = 0;
     90	if (ret < 0) {
     91		dev_err(dev, "Failed to select page %d (%d)\n", page, ret);
     92		return ret;
     93	}
     94
     95	dev_dbg(dev, "Selected page %d\n", page);
     96	ee1004_current_page = page;
     97
     98	return 0;
     99}
    100
    101static ssize_t ee1004_eeprom_read(struct i2c_client *client, char *buf,
    102				  unsigned int offset, size_t count)
    103{
    104	int status, page;
    105
    106	page = offset >> EE1004_PAGE_SHIFT;
    107	offset &= (1 << EE1004_PAGE_SHIFT) - 1;
    108
    109	status = ee1004_set_current_page(&client->dev, page);
    110	if (status)
    111		return status;
    112
    113	/* Can't cross page boundaries */
    114	if (offset + count > EE1004_PAGE_SIZE)
    115		count = EE1004_PAGE_SIZE - offset;
    116
    117	if (count > I2C_SMBUS_BLOCK_MAX)
    118		count = I2C_SMBUS_BLOCK_MAX;
    119
    120	return i2c_smbus_read_i2c_block_data_or_emulated(client, offset, count, buf);
    121}
    122
    123static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
    124			   struct bin_attribute *bin_attr,
    125			   char *buf, loff_t off, size_t count)
    126{
    127	struct i2c_client *client = kobj_to_i2c_client(kobj);
    128	size_t requested = count;
    129	int ret = 0;
    130
    131	/*
    132	 * Read data from chip, protecting against concurrent access to
    133	 * other EE1004 SPD EEPROMs on the same adapter.
    134	 */
    135	mutex_lock(&ee1004_bus_lock);
    136
    137	while (count) {
    138		ret = ee1004_eeprom_read(client, buf, off, count);
    139		if (ret < 0)
    140			goto out;
    141
    142		buf += ret;
    143		off += ret;
    144		count -= ret;
    145	}
    146out:
    147	mutex_unlock(&ee1004_bus_lock);
    148
    149	return ret < 0 ? ret : requested;
    150}
    151
    152static BIN_ATTR_RO(eeprom, EE1004_EEPROM_SIZE);
    153
    154static struct bin_attribute *ee1004_attrs[] = {
    155	&bin_attr_eeprom,
    156	NULL
    157};
    158
    159BIN_ATTRIBUTE_GROUPS(ee1004);
    160
    161static void ee1004_cleanup(int idx)
    162{
    163	if (--ee1004_dev_count == 0)
    164		while (--idx >= 0) {
    165			i2c_unregister_device(ee1004_set_page[idx]);
    166			ee1004_set_page[idx] = NULL;
    167		}
    168}
    169
    170static int ee1004_probe(struct i2c_client *client)
    171{
    172	int err, cnr = 0;
    173
    174	/* Make sure we can operate on this adapter */
    175	if (!i2c_check_functionality(client->adapter,
    176				     I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_READ_I2C_BLOCK) &&
    177	    !i2c_check_functionality(client->adapter,
    178				     I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA))
    179		return -EPFNOSUPPORT;
    180
    181	/* Use 2 dummy devices for page select command */
    182	mutex_lock(&ee1004_bus_lock);
    183	if (++ee1004_dev_count == 1) {
    184		for (cnr = 0; cnr < EE1004_NUM_PAGES; cnr++) {
    185			struct i2c_client *cl;
    186
    187			cl = i2c_new_dummy_device(client->adapter, EE1004_ADDR_SET_PAGE + cnr);
    188			if (IS_ERR(cl)) {
    189				err = PTR_ERR(cl);
    190				goto err_clients;
    191			}
    192			ee1004_set_page[cnr] = cl;
    193		}
    194
    195		/* Remember current page to avoid unneeded page select */
    196		err = ee1004_get_current_page();
    197		if (err < 0)
    198			goto err_clients;
    199		dev_dbg(&client->dev, "Currently selected page: %d\n", err);
    200		ee1004_current_page = err;
    201	} else if (client->adapter != ee1004_set_page[0]->adapter) {
    202		dev_err(&client->dev,
    203			"Driver only supports devices on a single I2C bus\n");
    204		err = -EOPNOTSUPP;
    205		goto err_clients;
    206	}
    207	mutex_unlock(&ee1004_bus_lock);
    208
    209	dev_info(&client->dev,
    210		 "%u byte EE1004-compliant SPD EEPROM, read-only\n",
    211		 EE1004_EEPROM_SIZE);
    212
    213	return 0;
    214
    215 err_clients:
    216	ee1004_cleanup(cnr);
    217	mutex_unlock(&ee1004_bus_lock);
    218
    219	return err;
    220}
    221
    222static int ee1004_remove(struct i2c_client *client)
    223{
    224	/* Remove page select clients if this is the last device */
    225	mutex_lock(&ee1004_bus_lock);
    226	ee1004_cleanup(EE1004_NUM_PAGES);
    227	mutex_unlock(&ee1004_bus_lock);
    228
    229	return 0;
    230}
    231
    232/*-------------------------------------------------------------------------*/
    233
    234static struct i2c_driver ee1004_driver = {
    235	.driver = {
    236		.name = "ee1004",
    237		.dev_groups = ee1004_groups,
    238	},
    239	.probe_new = ee1004_probe,
    240	.remove = ee1004_remove,
    241	.id_table = ee1004_ids,
    242};
    243module_i2c_driver(ee1004_driver);
    244
    245MODULE_DESCRIPTION("Driver for EE1004-compliant DDR4 SPD EEPROMs");
    246MODULE_AUTHOR("Jean Delvare");
    247MODULE_LICENSE("GPL");