nuttx/drivers/eeprom/i2c_xx24xx.c
Catalin Visinescu 8d2b71d127 drivers/efuse/efuse: Drivers Registered With World Write Permissions(Part 1)
Description:

In kernel builds, any unprivileged process running on the NuttX device
can open /dev/efuse and attempt to read/write fuse content. Reading the
fuses may provide valuable information to an attacker controlling the user
process. The write operation, in extreme cases where the fuse blocks are
not locked, may brick the device.

This is part of https://github.com/apache/nuttx/issues/19410

Compiles ok.

Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
2026-07-13 12:08:01 +02:00

1102 lines
29 KiB
C

/****************************************************************************
* drivers/eeprom/i2c_xx24xx.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/* This is a driver for I2C EEPROMs that use the same commands as the
* xx24xx.
*
* The following devices should be supported:
*
* Manufacturer Device Bytes PgSize AddrLen DevAddr
* Microchip
* 24xx00 16 1 1 1010000 Special case
* 24xx01 128 8 1 1010000
* 24xx02 256 8 1 1010000
* 24xx04 512 16 1 101000P
* 24xx08 1024 16 1 10100PP
* 24xx16 2048 16 1 1010PPP
* 24xx32 4096 32 2 1010AAA
* 24xx64 8192 32 2 1010AAA
* 24xx128 16384 64 2 1010AAA
* 24xx256 32768 64 2 1010AAA
* 24xx512 65536 128 2 1010AAA
* 24xx1025 131072 128 2 1010PAA Special case: address
* bit is shifted.
* 24xx1026 131072 128 2 1010AAP
*
* Atmel
* AT24C01 128 8 1 1010AAA
* AT24C02 256 8 1 1010AAA
* AT24C04 512 16 1 1010AAP P bits = word address
* AT24C08 1024 16 1 1010APP
* AT24C16 2048 16 1 1010PPP
* AT24C32 4096 32 2 1010AAA
* AT24C64 8192 32 2 1010AAA
* AT24C128 16384 64 2 10100AA
* AT24C256 32768 64 2 10100AA
* AT24C512 65536 128 2 10100AA
* AT24C1024 131072 256 2 10100AP
*
* ST Microelectronics
* M24C01 128 16 1 1010AAA
* M24C02 256 16 1 1010AAA
* M24C04 512 16 1 1010AAP
* M24C08 1024 16 1 1010APP
* M24C16 2048 16 1 1010PPP
* M24C32 4096 32 2 1010AAA ID pages supported
* as a separate device
* M24C64 8192 32 2 1010AAA
* M24128 16384 64 2 1010AAA
* M24256 32768 64 2 1010AAA
* M24512 65536 128 2 1010AAA
* M24M01 131072 256 2 1010AAP
* M24M02 262144 256 2 1010APP
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <sys/types.h>
#include <assert.h>
#include <nuttx/debug.h>
#include <errno.h>
#include <string.h>
#include <inttypes.h>
#include <nuttx/eeprom/eeprom.h>
#include <nuttx/fs/fs.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define UUID_SIZE 16
/****************************************************************************
* Types
****************************************************************************/
/* Device geometry description, compact form (2 bytes per entry) */
struct ee24xx_geom_s
{
uint8_t bytes : 4; /* Power of two of 128 bytes (0:128 ... 11:262144) */
uint8_t pagesize : 4; /* Power of two of 8 bytes (0:8 1:16 2:32 3:64 etc) */
uint8_t addrlen : 4; /* Nr of bytes in command address field */
uint8_t abits : 3; /* Nr of Address MSBits in the i2c device address LSBs */
uint8_t special : 1; /* Special device: uchip 24xx00 (total 16 bytes)
* or 24xx1025 (shifted P bits) */
};
/* Private data attached to the inode */
struct ee24xx_dev_s
{
/* Bus management */
FAR struct i2c_master_s *i2c; /* I2C device where the EEPROM is attached */
uint32_t freq; /* I2C bus speed */
uint8_t addr; /* 7-bit unshifted I2C device address */
/* Driver management */
mutex_t lock; /* file write access serialization */
uint8_t refs; /* Nr of times the device has been opened */
bool readonly; /* Flags */
/* Expanded from geometry */
uint32_t size; /* total bytes in device */
uint16_t pgsize; /* write block size, in bytes */
uint16_t addrlen; /* number of bytes in data addresses */
uint16_t haddrbits; /* Number of bits in high address part */
uint16_t haddrshift; /* bit-shift of high address part */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int ee24xx_open(FAR struct file *filep);
static int ee24xx_close(FAR struct file *filep);
static off_t ee24xx_seek(FAR struct file *filep, off_t offset, int whence);
static ssize_t ee24xx_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t ee24xx_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int ee24xx_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
#ifdef CONFIG_AT24CS_UUID
static ssize_t at24cs_read_uuid(FAR struct file *filep, FAR char *buffer,
size_t buflen);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/* Supported device geometries.
* One geometry can fit more than one device.
* The user will use an enum'ed index from include/eeprom/eeprom.h
*/
static const struct ee24xx_geom_s g_ee24xx_devices[] =
{
/* Microchip devices */
/* by pg al ab sp device bytes page alen */
{
0, 1, 1, 0, 1
}, /* 24xx00 16 1 1 Ridiculously small device */
{
0, 0, 1, 0, 0
}, /* 24xx01 128 8 1 */
{
1, 0, 1, 0, 0
}, /* 24xx02 256 8 1 */
{
2, 1, 1, 1, 0
}, /* 24xx04 512 16 1 */
{
3, 1, 1, 2, 0
}, /* 24xx08 1024 16 1 */
{
4, 1, 1, 3, 0
}, /* 24xx16 2048 16 1 */
{
5, 2, 2, 0, 0
}, /* 24xx32 4096 32 2 */
{
6, 2, 2, 0, 0
}, /* 24xx64 8192 32 2 */
{
7, 3, 2, 0, 0
}, /* 24xx128 16384 64 2 */
{
8, 3, 2, 0, 0
}, /* 24xx256 32768 64 2 */
{
9, 4, 2, 0, 0
}, /* 24xx512 65536 128 2 */
{
10, 4, 2, 1, 1
}, /* 24xx1025 131072 128 2 Shifted address, todo */
{
10, 4, 2, 1, 0
}, /* 24xx1026 131072 128 2 */
{
11, 5, 2, 2, 0
}, /* AT24CM02 262144 256 2 */
/* STM devices */
{
0, 1, 1, 0, 0
}, /* M24C01 128 16 1 */
{
1, 1, 1, 0, 0
}, /* M24C02 256 16 1 */
{
11, 5, 2, 2, 0
}, /* M24M02 262144 256 2 */
};
/* Driver operations */
static const struct file_operations g_ee24xx_fops =
{
ee24xx_open, /* open */
ee24xx_close, /* close */
ee24xx_read, /* read */
ee24xx_write, /* write */
ee24xx_seek, /* seek */
ee24xx_ioctl, /* ioctl */
};
#ifdef CONFIG_AT24CS_UUID
static const struct file_operations g_at24cs_uuid_fops =
{
ee24xx_open, /* piggyback on the ee24xx_open */
ee24xx_close, /* piggyback on the ee24xx_close */
at24cs_read_uuid, /* read */
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ee24xx_waitwritecomplete
*
* Use ACK polling to detect the completion of the write operation.
* Returns TRUE if write is complete (device replies to ACK).
*
****************************************************************************/
static int ee24xx_waitwritecomplete(FAR struct ee24xx_dev_s *eedev,
uint32_t memaddr)
{
struct i2c_msg_s msgs[1];
int ret;
int retries = 500;
uint8_t adr;
uint32_t addr_hi = (memaddr >> (eedev->addrlen << 3));
msgs[0].frequency = eedev->freq;
msgs[0].addr = eedev->addr |
(addr_hi & ((1 << eedev->haddrbits) - 1));
msgs[0].flags = I2C_M_READ;
msgs[0].buffer = &adr;
msgs[0].length = 1;
do
{
ret = I2C_TRANSFER(eedev->i2c, msgs, 1);
retries--;
}
while (ret != 0 && retries > 0);
return ret;
}
/****************************************************************************
* Name: ee24xx_writepage
*
* Description: Write data to the EEPROM, NOT crossing page boundaries.
* To avoid allocating a buffer to prepend the address, we are using 2 I2C
* messages while avoiding beginning the second one with a restart condition.
*
****************************************************************************/
static int ee24xx_writepage(FAR struct ee24xx_dev_s *eedev, uint32_t memaddr,
FAR const char *buffer, size_t len)
{
struct i2c_msg_s msgs[2];
uint8_t maddr[2];
uint32_t addr_hi = (memaddr >> (eedev->addrlen << 3));
/* Write data address */
maddr[0] = memaddr >> 8;
maddr[1] = memaddr & 0xff;
msgs[0].frequency = eedev->freq;
msgs[0].addr = eedev->addr |
(addr_hi & ((1 << eedev->haddrbits) - 1));
msgs[0].flags = 0;
msgs[0].buffer = eedev->addrlen == 2 ? &maddr[0] : &maddr[1];
msgs[0].length = eedev->addrlen;
/* Write data without a restart nor a control byte */
msgs[1].frequency = msgs[0].frequency;
msgs[1].addr = msgs[0].addr;
msgs[1].flags = I2C_M_NOSTART;
msgs[1].buffer = (FAR uint8_t *)buffer;
msgs[1].length = len;
return I2C_TRANSFER(eedev->i2c, msgs, 2);
}
/****************************************************************************
* Name: ee24xx_eraseall
*
* Description:
* Erase all data on the device
*
* Input Parameters:
* eedev - Device structure
*
****************************************************************************/
static int ee24xx_eraseall(FAR struct ee24xx_dev_s *eedev)
{
FAR char *buf;
off_t offset;
int ret;
DEBUGASSERT(eedev);
if (eedev->readonly)
{
return -EACCES;
}
buf = kmm_malloc(eedev->pgsize);
if (buf == NULL)
{
ferr("ERROR: Failed to allocate memory for ee24xx eraseall\n");
return -ENOMEM;
}
memset(buf, 0xff, eedev->pgsize);
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
goto free_buffer;
}
for (offset = 0; offset < eedev->size; offset += eedev->pgsize)
{
ret = ee24xx_writepage(eedev, offset, buf, eedev->pgsize);
if (ret < 0)
{
ferr("ERROR: Failed to write page at offset %" PRIdOFF
" (ret = %d)",
offset, ret);
goto release_semaphore;
}
ret = ee24xx_waitwritecomplete(eedev, offset);
if (ret < 0)
{
ferr("ERROR while waiting for write at offset %" PRIdOFF
" to complete (ret = %d)",
offset, ret);
goto release_semaphore;
}
}
release_semaphore:
nxmutex_unlock(&eedev->lock);
free_buffer:
kmm_free(buf);
return ret;
}
/****************************************************************************
* Name: ee24xx_erasepage
*
* Description:
* Erase 1 page of data
*
* Input Parameters:
* eedev - Device structure
* index - Index of the page to erase
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
static int ee24xx_erasepage(FAR struct ee24xx_dev_s *eedev,
unsigned long index)
{
FAR char *buf;
int ret;
off_t offset;
DEBUGASSERT(eedev);
DEBUGASSERT(eedev->pgsize > 0);
if (eedev->readonly)
{
return -EACCES;
}
if (index >= (eedev->size / eedev->pgsize))
{
return -EFBIG;
}
buf = kmm_malloc(eedev->pgsize);
if (buf == NULL)
{
ferr("ERROR: Failed to allocate memory for ee24xx_erasepage\n");
return -ENOMEM;
}
memset(buf, 0xff, eedev->pgsize);
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
goto free_buffer;
}
offset = index * eedev->pgsize;
ret = ee24xx_writepage(eedev, offset, buf, eedev->pgsize);
if (ret < 0)
{
ferr("ERROR: Failed to write page (ret = %d)", ret);
goto release_semaphore;
}
ret = ee24xx_waitwritecomplete(eedev, offset);
if (ret < 0)
{
ferr("ERROR while waiting for write to complete (ret = %d)", ret);
}
release_semaphore:
nxmutex_unlock(&eedev->lock);
free_buffer:
kmm_free(buf);
return ret;
}
/****************************************************************************
* Driver Functions
****************************************************************************/
/****************************************************************************
* Name: ee24xx_open
*
* Description: Open the block device
*
****************************************************************************/
static int ee24xx_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct ee24xx_dev_s *eedev;
int ret = OK;
DEBUGASSERT(inode->i_private);
eedev = inode->i_private;
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
return ret;
}
/* Increment the reference count */
if ((eedev->refs + 1) == 0)
{
ret = -EMFILE;
}
else
{
eedev->refs += 1;
}
nxmutex_unlock(&eedev->lock);
return ret;
}
/****************************************************************************
* Name: ee24xx_close
*
* Description: Close the block device
*
****************************************************************************/
static int ee24xx_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct ee24xx_dev_s *eedev;
int ret = OK;
DEBUGASSERT(inode->i_private);
eedev = inode->i_private;
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
return ret;
}
/* Decrement the reference count. I want the entire close operation
* to be atomic wrt other driver operations.
*/
if (eedev->refs == 0)
{
ret = -EIO;
}
else
{
eedev->refs -= 1;
}
nxmutex_unlock(&eedev->lock);
return ret;
}
/****************************************************************************
* Name: ee24xx_seek
*
* Remark: Copied from bchlib
*
****************************************************************************/
static off_t ee24xx_seek(FAR struct file *filep, off_t offset, int whence)
{
FAR struct ee24xx_dev_s *eedev;
off_t newpos;
int ret;
FAR struct inode *inode = filep->f_inode;
DEBUGASSERT(inode->i_private);
eedev = inode->i_private;
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
return ret;
}
/* Determine the new, requested file position
* For EEPROM sparse files are not allowed.
* "offset" can be negative, "newpos" must be between 0 and eedev->size
*/
switch (whence)
{
case SEEK_CUR:
newpos = filep->f_pos + offset;
if (newpos < 0 || newpos > eedev->size)
{
return -EINVAL;
}
break;
case SEEK_SET:
newpos = offset;
if (newpos < 0 || newpos > eedev->size)
{
return -EINVAL;
}
break;
case SEEK_END:
newpos = eedev->size + offset;
if (newpos < 0 || newpos > eedev->size)
{
return -EINVAL;
}
break;
default:
/* Return EINVAL if the whence argument is invalid */
nxmutex_unlock(&eedev->lock);
return -EINVAL;
}
/* Opengroup.org:
*
* "The lseek() function shall allow the file offset to be set beyond the
* end of the existing data in the file. If data is later written at
* this point, subsequent reads of data in the gap shall return bytes
* with the value 0 until data is actually written into the gap."
*
* We can conform to the first part, but not the second. But return -EINVAL
* if
*
* "...the resulting file offset would be negative for a regular file,
* block special file, or directory."
*/
if (newpos >= 0)
{
filep->f_pos = newpos;
ret = newpos;
finfo("SEEK newpos %" PRIdOFF "\n", newpos);
}
else
{
ret = -EINVAL;
}
nxmutex_unlock(&eedev->lock);
return ret;
}
/****************************************************************************
* Name: ee24xx_read
****************************************************************************/
static ssize_t ee24xx_read(FAR struct file *filep, FAR char *buffer,
size_t len)
{
FAR struct ee24xx_dev_s *eedev;
FAR struct inode *inode = filep->f_inode;
struct i2c_msg_s msgs[2];
uint8_t addr[2];
uint32_t addr_hi;
int ret;
DEBUGASSERT(inode->i_private);
eedev = inode->i_private;
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
return ret;
}
/* trim len if read would go beyond end of device */
if ((filep->f_pos + len) > eedev->size)
{
len = eedev->size - filep->f_pos;
}
if (len == 0)
{
/* We are at end of file */
ret = 0;
goto done;
}
/* Write data address */
finfo("READ %zu bytes at pos %" PRIdOFF "\n", len, filep->f_pos);
addr_hi = (filep->f_pos >> (eedev->addrlen << 3));
addr[0] = (filep->f_pos) >> 8;
addr[1] = (filep->f_pos) & 0xff;
msgs[0].frequency = eedev->freq;
msgs[0].addr = eedev->addr |
(addr_hi & ((1 << eedev->haddrbits) - 1));
msgs[0].flags = 0;
msgs[0].buffer = eedev->addrlen == 2 ? &addr[0] : &addr[1];
msgs[0].length = eedev->addrlen;
/* Read data */
msgs[1].frequency = msgs[0].frequency;
msgs[1].addr = msgs[0].addr;
msgs[1].flags = I2C_M_READ;
msgs[1].buffer = (FAR uint8_t *)buffer;
msgs[1].length = len;
ret = I2C_TRANSFER(eedev->i2c, msgs, 2);
if (ret < 0)
{
goto done;
}
ret = len;
/* Update the file position */
filep->f_pos += len;
done:
nxmutex_unlock(&eedev->lock);
return ret;
}
/****************************************************************************
* Name: at24cs_read_uuid
****************************************************************************/
#ifdef CONFIG_AT24CS_UUID
static ssize_t at24cs_read_uuid(FAR struct file *filep, FAR char *buffer,
size_t len)
{
FAR struct ee24xx_dev_s *eedev;
FAR struct inode *inode = filep->f_inode;
struct i2c_msg_s msgs[2];
uint8_t regindx;
int ret;
DEBUGASSERT(inode->i_private);
eedev = inode->i_private;
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
return ret;
}
/* trim len if read would go beyond end of device */
if ((filep->f_pos + len) > UUID_SIZE)
{
len = UUID_SIZE - filep->f_pos;
}
if (len == 0)
{
/* We are at end of file */
ret = 0;
goto done;
}
/* Write data address */
finfo("READ %zu bytes at pos %" PRIdOFF "\n", len, filep->f_pos);
regindx = 0x80; /* reg index of UUID[0] */
msgs[0].frequency = eedev->freq;
msgs[0].addr = eedev->addr + 8; /* slave addr of UUID */
msgs[0].flags = 0;
msgs[0].buffer = &regindx;
msgs[0].length = 1;
/* Read data */
msgs[1].frequency = msgs[0].frequency;
msgs[1].addr = msgs[0].addr;
msgs[1].flags = I2C_M_READ;
msgs[1].buffer = (FAR uint8_t *)buffer;
msgs[1].length = len;
ret = I2C_TRANSFER(eedev->i2c, msgs, 2);
if (ret < 0)
{
goto done;
}
ret = len;
/* Update the file position */
filep->f_pos += len;
done:
nxmutex_unlock(&eedev->lock);
return ret;
}
#endif
/****************************************************************************
* Name: ee24xx_write
****************************************************************************/
static ssize_t ee24xx_write(FAR struct file *filep, FAR const char *buffer,
size_t len)
{
FAR struct ee24xx_dev_s *eedev;
size_t cnt;
int pageoff;
FAR struct inode *inode = filep->f_inode;
int ret = -EACCES;
int savelen;
DEBUGASSERT(inode->i_private);
eedev = inode->i_private;
if (eedev->readonly)
{
return ret;
}
/* Forbid writes past the end of the device */
if (filep->f_pos >= eedev->size)
{
return -EFBIG;
}
finfo("Entering with len=%zu\n", len);
/* Clamp len to avoid crossing the end of the memory */
if ((len + filep->f_pos) > eedev->size)
{
len = eedev->size - filep->f_pos;
finfo("Len clamped to %zu\n", len);
}
savelen = len; /* save number of bytes written */
ret = nxmutex_lock(&eedev->lock);
if (ret < 0)
{
return ret;
}
/* Writes can't happen in a row like the read does.
* The EEPROM is made of pages, and write sequences
* cannot cross page boundaries. So every time the last
* byte of a page is programmed, a separate I2C transaction
* required to continue writing.
*/
/* First, write some page-unaligned data */
pageoff = filep->f_pos & (eedev->pgsize - 1);
cnt = eedev->pgsize - pageoff;
if (cnt > len)
{
cnt = len;
}
if (pageoff > 0)
{
finfo("First %zu unaligned bytes at %" PRIdOFF " (pageoff %d)\n",
cnt, filep->f_pos, pageoff);
ret = ee24xx_writepage(eedev, filep->f_pos, buffer, cnt);
if (ret < 0)
{
ferr("write failed, ret = %d\n", ret);
goto done;
}
ret = ee24xx_waitwritecomplete(eedev, filep->f_pos);
if (ret < 0)
{
ferr("writecomplete failed, ret = %d\n", ret);
goto done;
}
len -= cnt;
buffer += cnt;
filep->f_pos += cnt;
}
/* Then, write remaining bytes at page-aligned addresses */
while (len > 0)
{
cnt = len;
if (cnt > eedev->pgsize)
{
cnt = eedev->pgsize;
}
finfo("Aligned page write for %zu bytes at %" PRIdOFF "\n",
cnt, filep->f_pos);
ret = ee24xx_writepage(eedev, filep->f_pos, buffer, cnt);
if (ret < 0)
{
ferr("write failed, ret = %d\n", ret);
goto done;
}
ret = ee24xx_waitwritecomplete(eedev, filep->f_pos);
if (ret < 0)
{
ferr("writecomplete failed, ret = %d\n", ret);
goto done;
}
len -= cnt;
buffer += cnt;
filep->f_pos += cnt;
}
ret = savelen;
done:
nxmutex_unlock(&eedev->lock);
return ret;
}
/****************************************************************************
* Name: ee24xx_ioctl
*
* Description: TODO: Erase a sector/page/device or read device ID / MAC.
* This is completely optional.
*
****************************************************************************/
static int ee24xx_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct ee24xx_dev_s *eedev;
FAR struct inode *inode = filep->f_inode;
int ret = -EINVAL;
DEBUGASSERT(inode->i_private);
eedev = inode->i_private;
switch (cmd)
{
case EEPIOC_GEOMETRY:
{
FAR struct eeprom_geometry_s *geo =
(FAR struct eeprom_geometry_s *)arg;
if (geo != NULL)
{
geo->npages = 0;
geo->pagesize = eedev->pgsize;
geo->sectsize = eedev->pgsize;
if (eedev->pgsize > 0)
{
geo->npages = eedev->size / eedev->pgsize;
}
ret = OK;
}
}
break;
case EEPIOC_SETSPEED:
{
ret = nxmutex_lock(&eedev->lock);
if (ret == OK)
{
eedev->freq = (uint32_t)arg;
nxmutex_unlock(&eedev->lock);
}
}
break;
case EEPIOC_PAGEERASE:
case EEPIOC_SECTORERASE:
ret = ee24xx_erasepage(eedev, arg);
break;
case EEPIOC_CHIPERASE:
ret = ee24xx_eraseall(eedev);
break;
/* TODO: add "case EEPIOC_BLOCKPROTECT:" */
default:
ret = -ENOTTY;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ee24xx_initialize
*
* Description: Bind a EEPROM driver to an I2C bus. The user MUST provide
* a description of the device geometry, since it is not possible to read
* this information from the device (contrary to the SPI flash devices).
*
****************************************************************************/
int ee24xx_initialize(FAR struct i2c_master_s *bus, uint8_t devaddr,
FAR char *devname, int devtype, int readonly)
{
FAR struct ee24xx_dev_s *eedev;
#ifdef CONFIG_AT24CS_UUID
FAR char *uuidname;
size_t size;
int ret;
#endif
/* Check device type early */
if ((devtype < 0) ||
(devtype >= sizeof(g_ee24xx_devices) / sizeof(g_ee24xx_devices[0])))
{
return -EINVAL;
}
eedev = kmm_zalloc(sizeof(struct ee24xx_dev_s));
if (!eedev)
{
return -ENOMEM;
}
nxmutex_init(&eedev->lock);
eedev->freq = CONFIG_EE24XX_FREQUENCY;
eedev->i2c = bus;
eedev->addr = devaddr;
eedev->readonly = !!readonly;
/* Expand device geometry from compacted info */
eedev->size = 128 << g_ee24xx_devices[devtype].bytes;
eedev->pgsize = 8 << g_ee24xx_devices[devtype].pagesize;
eedev->addrlen = g_ee24xx_devices[devtype].addrlen;
eedev->haddrbits = g_ee24xx_devices[devtype].abits;
eedev->haddrshift = 0;
/* Apply special properties */
if (g_ee24xx_devices[devtype].special)
{
if (devtype == EEPROM_24XX00)
{
/* Ultra small 16-byte EEPROM */
eedev->size = 16;
/* The device only has BYTE write,
* which is emulated with 1-byte pages
*/
eedev->pgsize = 1;
}
else if (devtype == EEPROM_24XX1025)
{
/* Microchip alien part where the address MSB is << 2 bits */
ferr("Device 24xx1025 is not supported for the moment, TODO.\n");
eedev->haddrshift = 2;
kmm_free(eedev);
return -ENODEV;
}
}
finfo("EEPROM device %s, %" PRIu32 " bytes, %d per page, addrlen %d, %s\n",
devname, eedev->size, eedev->pgsize, eedev->addrlen,
eedev->readonly ? "readonly" : "");
#ifdef CONFIG_AT24CS_UUID
size = strlen(devname) + 8;
uuidname = kmm_zalloc(size);
if (!uuidname)
{
return -ENOMEM;
}
/* register the UUID I2C slave with the same name as the parent
* EEPROM chip, but with the ".uuid" suffix
*/
strlcpy(uuidname, devname, size);
strlcat(uuidname, ".uuid", size);
ret = register_driver(uuidname, &g_at24cs_uuid_fops, 0400, eedev);
kmm_free(uuidname);
if (OK != ret)
{
ferr("register uuid failed, ret = %d\n", ret);
return ret;
}
#endif
return register_driver_with_size(devname, &g_ee24xx_fops, 0600, eedev,
eedev->size);
}