minix/drivers/cat24c256/cat24c256.c
David van Moolenbroek 6700874deb Block drivers: make IOCTL request unsigned long
The block driver protocol and libblockdriver's bdr_ioctl hook are
changed, as well as the users of this hook. Other parts of the system
are expected to change accordingly eventually, since the ioctl(2)
prototype has been aligned with NetBSD's.

Change-Id: Ide46245b22cfa89ed267a38088fb0ab7696eba92
2014-02-19 11:22:15 +01:00

517 lines
12 KiB
C

#include <minix/blockdriver.h>
#include <minix/com.h>
#include <minix/drivers.h>
#include <minix/ds.h>
#include <minix/i2c.h>
#include <minix/i2cdriver.h>
#include <minix/log.h>
#include <stdio.h>
#include <stdlib.h>
/* constants */
#define NR_DEVS 1 /* number of devices this driver handles */
#define EEPROM_DEV 0 /* index of eeprom device */
/* When passing data over a grant one needs to pass
* a buffer to sys_safecopy copybuff is used for that*/
#define COPYBUF_SIZE 0x1000 /* 4k buff */
static unsigned char copybuf[COPYBUF_SIZE];
static i2c_addr_t valid_addrs[9] = {
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x00
};
/* SEF functions and variables. */
static void sef_local_startup(void);
static int sef_cb_init(int type, sef_init_info_t * info);
static int sef_cb_lu_state_save(int);
static int lu_state_restore(void);
/* libblockdriver callbacks */
static int cat24c256_blk_open(devminor_t minor, int access);
static int cat24c256_blk_close(devminor_t minor);
static ssize_t cat24c256_blk_transfer(devminor_t minor, int do_write,
u64_t pos, endpoint_t endpt, iovec_t * iov, unsigned int count, int flags);
static int cat24c256_blk_ioctl(devminor_t minor, unsigned long request,
endpoint_t endpt, cp_grant_id_t grant, endpoint_t user_endpt);
static struct device *cat24c256_blk_part(devminor_t minor);
static void cat24c256_blk_other(message * m, int ipc_status);
/* Entry points into the device dependent code of block drivers. */
struct blockdriver cat24c256_tab = {
.bdr_type = BLOCKDRIVER_TYPE_OTHER,
.bdr_open = cat24c256_blk_open,
.bdr_close = cat24c256_blk_close,
.bdr_transfer = cat24c256_blk_transfer,
.bdr_ioctl = cat24c256_blk_ioctl, /* always returns ENOTTY */
.bdr_part = cat24c256_blk_part,
.bdr_other = cat24c256_blk_other /* for notify events from DS */
};
static int cat24c256_read128(uint16_t memaddr, void *buf, size_t buflen, int flags);
static int cat24c256_read(uint16_t memaddr, void *buf, size_t buflen, int flags);
static int cat24c256_write16(uint16_t memaddr, void *buf, size_t buflen, int flags);
static int cat24c256_write(uint16_t memaddr, void *buf, size_t buflen, int flags);
/* globals */
/* counts the number of times a device file is open */
static int openct[NR_DEVS];
/* base and size of each device */
static struct device geom[NR_DEVS];
/* the bus that this device is on (counting starting at 1) */
static uint32_t bus;
/* slave address of the device */
static i2c_addr_t address;
/* endpoint for the driver for the bus itself. */
static endpoint_t bus_endpoint;
/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
static struct log log = {
.name = "cat24c256",
.log_level = LEVEL_INFO,
.log_func = default_log
};
static int
cat24c256_blk_open(devminor_t minor, int access)
{
log_trace(&log, "cat24c256_blk_open(%d,%d)\n", minor, access);
if (cat24c256_blk_part(minor) == NULL) {
return ENXIO;
}
openct[minor]++;
return OK;
}
static int
cat24c256_blk_close(devminor_t minor)
{
log_trace(&log, "cat24c256_blk_close(%d)\n", minor);
if (cat24c256_blk_part(minor) == NULL) {
return ENXIO;
}
if (openct[minor] < 1) {
log_warn(&log, "closing unopened device %d\n", minor);
return EINVAL;
}
openct[minor]--;
return OK;
}
static ssize_t
cat24c256_blk_transfer(devminor_t minor, int do_write, u64_t pos64,
endpoint_t endpt, iovec_t * iov, unsigned int nr_req, int flags)
{
/* Read or write one the driver's block devices. */
unsigned int count;
struct device *dv;
u64_t dv_size;
int r;
u64_t position;
cp_grant_id_t grant;
ssize_t total = 0;
vir_bytes offset;
log_trace(&log, "cat24c256_blk_transfer()\n");
/* Get minor device information. */
dv = cat24c256_blk_part(minor);
if (dv == NULL) {
return ENXIO;
}
if (nr_req > NR_IOREQS) {
return EINVAL;
}
dv_size = dv->dv_size;
if (pos64 > dv_size) {
return OK; /* Beyond EOF */
}
position = pos64;
offset = 0;
while (nr_req > 0) {
/* How much to transfer and where to / from. */
count = iov->iov_size;
grant = (cp_grant_id_t) iov->iov_addr;
/* check for EOF */
if (position >= dv_size) {
return total;
}
/* don't go past the end of the device */
if (position + count > dv_size) {
count = dv_size - position;
}
/* don't overflow copybuf */
if (count > COPYBUF_SIZE) {
count = COPYBUF_SIZE;
}
log_trace(&log, "transfering 0x%x bytes\n", count);
if (do_write) {
r = sys_safecopyfrom(endpt, grant, (vir_bytes) offset,
(vir_bytes) copybuf, count);
if (r != OK) {
log_warn(&log, "safecopyfrom failed\n");
return EINVAL;
}
r = cat24c256_write(position, copybuf, count, flags);
if (r != OK) {
log_warn(&log, "write failed (r=%d)\n", r);
return r;
}
} else {
r = cat24c256_read(position, copybuf, count, flags);
if (r != OK) {
log_warn(&log, "read failed (r=%d)\n", r);
return r;
}
r = sys_safecopyto(endpt, grant, (vir_bytes) offset,
(vir_bytes) copybuf, count);
if (r != OK) {
log_warn(&log, "safecopyto failed\n");
return EINVAL;
}
}
/* Book the number of bytes transferred. */
position += count;
total += count;
offset += count;
/* only go on to the next iov when this one is full */
if ((iov->iov_size -= count) == 0) {
iov++;
nr_req--;
offset = 0;
}
}
return total;
}
static int
cat24c256_blk_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt,
cp_grant_id_t grant, endpoint_t UNUSED(user_endpt))
{
log_trace(&log, "cat24c256_blk_ioctl(%d)\n", minor);
/* no supported ioctls for this device */
return ENOTTY;
}
static struct device *
cat24c256_blk_part(devminor_t minor)
{
log_trace(&log, "cat24c256_blk_part(%d)\n", minor);
if (minor < 0 || minor >= NR_DEVS) {
return NULL;
}
return &geom[minor];
}
static void
cat24c256_blk_other(message * m, int ipc_status)
{
log_trace(&log, "cat24c256_blk_other(0x%x)\n", m->m_type);
if (is_ipc_notify(ipc_status)) {
if (m->m_source == DS_PROC_NR) {
log_debug(&log,
"bus driver changed state, update endpoint\n");
i2cdriver_handle_bus_update(&bus_endpoint, bus,
address);
}
} else
log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
}
/* The lower level i2c interface can only read/write 128 bytes at a time.
* One might want to do more I/O than that at once w/EEPROM, so there is
* cat24c256_read() and cat24c256_read128(). cat24c256_read128() does the
* actual reading in chunks up to 128 bytes. cat24c256_read() splits
* the request up into chunks and repeatedly calls cat24c256_read128()
* until all of the requested EEPROM locations have been read.
*/
static int
cat24c256_read128(uint16_t memaddr, void *buf, size_t buflen, int flags)
{
int r;
minix_i2c_ioctl_exec_t ioctl_exec;
if (buflen > I2C_EXEC_MAX_BUFLEN || buf == NULL
|| (((uint16_t) (memaddr + buflen)) < memaddr)) {
log_warn(&log,
"buflen exceeded max or buf == NULL or would overflow\n");
return -1;
}
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
ioctl_exec.iie_op = I2C_OP_READ_WITH_STOP;
ioctl_exec.iie_addr = address;
/* set the memory address to read from */
if ((BDEV_NOPAGE & flags) == BDEV_NOPAGE) {
/* reading within the current page */
ioctl_exec.iie_cmd[0] = (memaddr & 0xff);
ioctl_exec.iie_cmdlen = 1;
} else {
ioctl_exec.iie_cmd[0] = ((memaddr >> 8) & 0xff);
ioctl_exec.iie_cmd[1] = (memaddr & 0xff);
ioctl_exec.iie_cmdlen = 2;
}
ioctl_exec.iie_buflen = buflen;
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
if (r != OK) {
return r;
}
/* call was good, copy results to caller's buffer */
memcpy(buf, ioctl_exec.iie_buf, buflen);
log_debug(&log, "Read %d bytes from 0x%x 0x%x OK\n", buflen,
ioctl_exec.iie_cmd[0], ioctl_exec.iie_cmd[1]);
return OK;
}
int
cat24c256_read(uint16_t memaddr, void *buf, size_t buflen, int flags)
{
int r;
uint16_t i;
if (buf == NULL || ((memaddr + buflen) < memaddr)) {
log_warn(&log, "buf == NULL or would overflow\n");
return -1;
}
for (i = 0; i < buflen; i += 128) {
r = cat24c256_read128(memaddr + i, buf + i,
((buflen - i) < 128) ? (buflen - i) : 128, flags);
if (r != OK) {
return r;
}
log_trace(&log, "read %d bytes starting at 0x%x\n",
((buflen - i) < 128) ? (buflen - i) : 128, memaddr + i);
}
return OK;
}
static int
cat24c256_write16(uint16_t memaddr, void *buf, size_t buflen, int flags)
{
int r;
int addrlen;
minix_i2c_ioctl_exec_t ioctl_exec;
if (buflen > (I2C_EXEC_MAX_BUFLEN - 2) || buf == NULL
|| (((uint16_t) (memaddr + buflen + 2)) < memaddr)) {
log_warn(&log,
"buflen exceeded max or buf == NULL or would overflow\n");
return -1;
}
memset(&ioctl_exec, '\0', sizeof(minix_i2c_ioctl_exec_t));
ioctl_exec.iie_op = I2C_OP_WRITE_WITH_STOP;
ioctl_exec.iie_addr = address;
ioctl_exec.iie_cmdlen = 0;
/* set the memory address to write to */
if ((BDEV_NOPAGE & flags) == BDEV_NOPAGE) {
/* writing within the current page */
ioctl_exec.iie_buf[0] = (memaddr & 0xff); /* address */
addrlen = 1;
} else {
ioctl_exec.iie_buf[0] = ((memaddr >> 8) & 0xff);/* page */
ioctl_exec.iie_buf[1] = (memaddr & 0xff); /* address */
addrlen = 2;
}
memcpy(ioctl_exec.iie_buf + addrlen, buf, buflen);
ioctl_exec.iie_buflen = buflen + addrlen;
r = i2cdriver_exec(bus_endpoint, &ioctl_exec);
if (r != OK) {
return r;
}
log_debug(&log, "Wrote %d bytes to 0x%x 0x%x OK - First = 0x%x\n",
buflen, ioctl_exec.iie_buf[0], ioctl_exec.iie_buf[1],
ioctl_exec.iie_buf[2]);
return OK;
}
int
cat24c256_write(uint16_t memaddr, void *buf, size_t buflen, int flags)
{
int r;
uint16_t i;
if (buf == NULL || ((memaddr + buflen) < memaddr)) {
log_warn(&log, "buf == NULL or would overflow\n");
return -1;
}
for (i = 0; i < buflen; i += 16) {
r = cat24c256_write16(memaddr + i, buf + i,
((buflen - i) < 16) ? (buflen - i) : 16, flags);
if (r != OK) {
return r;
}
log_trace(&log, "wrote %d bytes starting at 0x%x\n",
((buflen - i) < 16) ? (buflen - i) : 16, memaddr + i);
}
return OK;
}
static int
sef_cb_lu_state_save(int UNUSED(state))
{
ds_publish_u32("bus", bus, DSF_OVERWRITE);
ds_publish_u32("address", address, DSF_OVERWRITE);
return OK;
}
static int
lu_state_restore(void)
{
/* Restore the state. */
u32_t value;
ds_retrieve_u32("bus", &value);
ds_delete_u32("bus");
bus = (int) value;
ds_retrieve_u32("address", &value);
ds_delete_u32("address");
address = (int) value;
return OK;
}
static int
sef_cb_init(int type, sef_init_info_t * UNUSED(info))
{
int r;
if (type == SEF_INIT_LU) {
/* Restore the state. */
lu_state_restore();
}
geom[EEPROM_DEV].dv_base = ((u64_t) (0));
geom[EEPROM_DEV].dv_size = ((u64_t) (32768));
/* look-up the endpoint for the bus driver */
bus_endpoint = i2cdriver_bus_endpoint(bus);
if (bus_endpoint == 0) {
log_warn(&log, "Couldn't find bus driver.\n");
return EXIT_FAILURE;
}
/* claim the EEPROM device */
r = i2cdriver_reserve_device(bus_endpoint, address);
if (r != OK) {
log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
address, r);
return EXIT_FAILURE;
}
if (type != SEF_INIT_LU) {
/* sign up for updates about the i2c bus going down/up */
r = i2cdriver_subscribe_bus_updates(bus);
if (r != OK) {
log_warn(&log, "Couldn't subscribe to bus updates\n");
return EXIT_FAILURE;
}
i2cdriver_announce(bus);
blockdriver_announce(type);
log_debug(&log, "announced\n");
}
return OK;
}
static void
sef_local_startup(void)
{
/*
* Register init callbacks. Use the same function for all event types
*/
sef_setcb_init_fresh(sef_cb_init);
sef_setcb_init_lu(sef_cb_init);
sef_setcb_init_restart(sef_cb_init);
/*
* Register live update callbacks.
*/
/* Agree to update immediately when LU is requested in a valid state. */
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
/* Support live update starting from any standard state. */
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
/* Register a custom routine to save the state. */
sef_setcb_lu_state_save(sef_cb_lu_state_save);
/* Let SEF perform startup. */
sef_startup();
}
int
main(int argc, char *argv[])
{
int r;
env_setargs(argc, argv);
r = i2cdriver_env_parse(&bus, &address, valid_addrs);
if (r < 0) {
log_warn(&log, "Expecting -args 'bus=X address=0xYY'\n");
log_warn(&log, "Example -args 'bus=3 address=0x54'\n");
return EXIT_FAILURE;
} else if (r > 0) {
log_warn(&log,
"Invalid slave address for device, expecting 0x50-0x57\n");
return EXIT_FAILURE;
}
sef_local_startup();
log_debug(&log, "Startup Complete\n");
blockdriver_task(&cat24c256_tab);
log_debug(&log, "Shutting down\n");
return OK;
}