1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2025 Oxide Computer Company
14 */
15
16 /*
17 * Basic emulation of the AT24C family devices.
18 */
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include <err.h>
23 #include <sys/sysmacros.h>
24
25 #include "i2csimd.h"
26
27 typedef struct at24c {
28 uint8_t *at_data;
29 uint32_t at_len;
30 uint8_t at_page;
31 bool at_addr16;
32 uint16_t at_curaddr;
33 } at24c_t;
34
35 static bool
at24c_write(void * arg,uint32_t len,const uint8_t * buf)36 at24c_write(void *arg, uint32_t len, const uint8_t *buf)
37 {
38 at24c_t *at24c = arg;
39
40 /*
41 * If we have no data to write, then we're done and that's fine.
42 */
43 if (len == 0) {
44 return (true);
45 }
46
47 /*
48 * The first byte or two (depending on the part) always indicates the
49 * address that we should write to. If we require a 2-byte address but
50 * we don't have enough bytes, it's not quite clear what the device
51 * expects. We just treat it as setting the high byte.
52 */
53 if (at24c->at_addr16) {
54 at24c->at_curaddr = buf[0] << 8;
55 if (len == 1) {
56 return (true);
57 }
58 at24c->at_curaddr |= buf[1];
59 len -= 2;
60 buf += 2;
61 } else {
62 at24c->at_curaddr = buf[0];
63 len--;
64 buf++;
65 }
66
67 /*
68 * Now that we've set the address, write to data within the page. Note
69 * that once we hit the end of the page, we go back to the start of the
70 * page.
71 */
72 while (len > 0) {
73 uint32_t page_start = at24c->at_curaddr &
74 ~(at24c->at_page - 1);
75 uint32_t page_end = page_start + at24c->at_page - 1;
76 uint32_t page_rem = page_end - at24c->at_curaddr + 1;
77 uint32_t towrite = MIN(page_rem, len);
78
79 (void) memcpy(&at24c->at_data[at24c->at_curaddr], buf, towrite);
80 len -= towrite;
81 buf += towrite;
82 at24c->at_curaddr += towrite;
83 if (at24c->at_curaddr == page_end + 1) {
84 at24c->at_curaddr = page_start;
85 }
86 }
87
88 return (true);
89 }
90
91 static bool
at24c_read(void * arg,uint32_t len,uint8_t * buf)92 at24c_read(void *arg, uint32_t len, uint8_t *buf)
93 {
94 at24c_t *at24c = arg;
95
96 /*
97 * Read from the current device offset. It should be incremented when
98 * we're done. If we read the entire device, then we wrap around to the
99 * start.
100 */
101 while (len > 0) {
102 uint16_t rem = at24c->at_len - at24c->at_curaddr + 1;
103 uint16_t toread = MIN(rem, len);
104
105 (void) memcpy(buf, &at24c->at_data[at24c->at_curaddr], toread);
106 len -= toread;
107 buf += toread;
108 at24c->at_curaddr += toread;
109 if (at24c->at_curaddr == at24c->at_len)
110 at24c->at_curaddr = 0;
111 }
112
113 return (true);
114 }
115
116 static const i2csimd_ops_t at24c_ops = {
117 .sop_write = at24c_write,
118 .sop_read = at24c_read
119 };
120
121 static i2csimd_dev_t *
i2csimd_make_at24c(uint8_t addr,const char * data,size_t dlen,uint32_t len,uint8_t page,bool addr16)122 i2csimd_make_at24c(uint8_t addr, const char *data, size_t dlen, uint32_t len,
123 uint8_t page, bool addr16)
124 {
125 at24c_t *at24c = calloc(1, sizeof (at24c_t));
126 if (at24c == NULL) {
127 err(EXIT_FAILURE, "failed to allocate at24c");
128 }
129
130 at24c->at_len = len;
131 at24c->at_data = calloc(sizeof (uint8_t), len);
132 if (at24c->at_data == NULL) {
133 err(EXIT_FAILURE, "failed to allocate %u bytes for data", len);
134 }
135
136 (void) memset(at24c->at_data, 0xff, at24c->at_len);
137 if (dlen > 0) {
138 (void) memcpy(at24c->at_data, data, dlen);
139 }
140
141 at24c->at_page = page;
142 at24c->at_addr16 = addr16;
143
144 i2csimd_dev_t *dev = calloc(1, sizeof (i2csimd_dev_t));
145 if (dev == NULL) {
146 err(EXIT_FAILURE, "failed to allocate i2csimd_dev_t");
147 }
148
149 dev->dev_name = "at24c";
150 dev->dev_addr = addr;
151 dev->dev_arg = at24c;
152 dev->dev_ops = &at24c_ops;
153
154 return (dev);
155 }
156
157 i2csimd_dev_t *
i2csimd_make_at24c32(uint8_t addr,const char * data,size_t len)158 i2csimd_make_at24c32(uint8_t addr, const char *data, size_t len)
159 {
160 return (i2csimd_make_at24c(addr, data, len, 4096, 32, true));
161 }
162
163 /*
164 * This makes a 256-bit AT24C slice that is 256 bytes wide. This is designed for
165 * the multi-address devices like the at24c04 or at24c16.
166 */
167 i2csimd_dev_t *
i2csimd_make_at24cXX(uint8_t addr,const char * data,size_t len)168 i2csimd_make_at24cXX(uint8_t addr, const char *data, size_t len)
169 {
170 return (i2csimd_make_at24c(addr, data, len, 256, 16, false));
171 }
172