sdmmc.c revision 1.1
1/* $NetBSD: sdmmc.c,v 1.1 2025/11/16 20:11:47 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2025 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <lib/libsa/stand.h>
30#include <lib/libkern/libkern.h>
31
32#include <sys/bootblock.h>
33
34#define FSTYPENAMES
35#include <sys/disklabel.h>
36
37#include "sdmmc.h"
38#include "miniipc.h"
39#include "gpio.h"
40
41#if LABELSECTOR != 1
42#error bad LABELSECTOR
43#endif
44
45#define DEFAULT_DEVICE	"ld0"
46#define DEFAULT_PART	0
47
48#define	SDMMC_STATE_NEW_CARD		2
49
50static uint8_t sdmmc_buf[SDMMC_BLOCK_SIZE];
51static struct disklabel sdmmc_disklabel;
52
53static void
54sdmmc_print_label(void)
55{
56#ifdef SDMMC_DEBUG
57	struct disklabel *d = &sdmmc_disklabel;
58	int part;
59
60	for (part = 0; part < le16toh(d->d_npartitions); part++) {
61		struct partition *p = &d->d_partitions[part];
62
63		printf("partition %s%c ", DEFAULT_DEVICE, part + 'a');
64
65		if (p->p_fstype < __arraycount(fstypenames)) {
66			printf("(%s)", fstypenames[p->p_fstype]);
67		} else {
68			printf("(type %u)", p->p_fstype);
69		}
70
71		printf(" - %u %u\n", le32toh(p->p_offset), le32toh(p->p_size));
72	}
73#endif
74}
75
76static int
77sdmmc_read_label(daddr_t dblk, size_t size)
78{
79	struct disklabel d;
80	int error;
81
82	error = miniipc_sdmmc_read(dblk + LABELSECTOR, 1, sdmmc_buf);
83	if (error != 0) {
84		printf("SDMMC: failed to read disklabel: %d\n", error);
85		return error;
86	}
87	memcpy(&d, sdmmc_buf, sizeof(d));
88
89	if (le32toh(d.d_magic) != DISKMAGIC ||
90	    le32toh(d.d_magic2) != DISKMAGIC) {
91#ifdef SDMMC_DEBUG
92		printf("SDMMC: bad diskmagic 0x%x 0x%x\n", le32toh(d.d_magic),
93		    le32toh(d.d_magic2));
94#endif
95		return EINVAL;
96	}
97	if (le16toh(d.d_npartitions) > MAXPARTITIONS) {
98		printf("SDMMC: bad npartitions %u\n", le16toh(d.d_npartitions));
99		return EINVAL;
100	}
101
102	memcpy(&sdmmc_disklabel, sdmmc_buf, sizeof(sdmmc_disklabel));
103	return 0;
104}
105
106int
107sdmmc_init(void)
108{
109	struct mbr_sector mbr;
110	struct mbr_partition *mbr_part;
111	int error, n;
112	bool has_label = false;
113	uint32_t state, ack;
114
115	miniipc_sdmmc_state(&state);
116	if (state == SDMMC_STATE_NEW_CARD) {
117		miniipc_sdmmc_ack(&ack);
118	}
119
120	error = miniipc_sdmmc_read(0, 1, sdmmc_buf);
121	if (error != 0) {
122		printf("SDMMC: failed to read MBR: %d\n", error);
123		return error;
124	}
125	memcpy(&mbr, sdmmc_buf, sizeof(mbr));
126	if (le16toh(mbr.mbr_magic) != MBR_MAGIC) {
127		printf("SDMMC: bad MBR magic: 0x%x\n", le32toh(mbr.mbr_magic));
128		return ENXIO;
129	}
130
131	for (n = 0; n < MBR_PART_COUNT; n++) {
132		uint32_t start, size;
133
134		mbr_part = &mbr.mbr_parts[n];
135		size = le32toh(mbr_part->mbrp_size);
136		if (le32toh(mbr_part->mbrp_size) == 0) {
137			continue;
138		}
139		start = le32toh(mbr_part->mbrp_start);
140#ifdef SDMMC_DEBUG
141		printf("MBR part %u type 0x%x start %u size %u\n",
142		    n, mbr_part->mbrp_type, start, size);
143#endif
144
145		if (mbr_part->mbrp_type == MBR_PTYPE_NETBSD && !has_label) {
146			error = sdmmc_read_label(start, size);
147			if (error != 0) {
148				struct partition *p =
149				    &sdmmc_disklabel.d_partitions[0];
150
151				/* No label on disk, fake one. */
152				p->p_fstype = FS_BSDFFS;
153				p->p_size = htole32(size);
154				p->p_offset = htole32(start);
155				sdmmc_disklabel.d_npartitions = htole16(1);
156			}
157			has_label = true;
158		}
159	}
160
161	sdmmc_print_label();
162
163	return 0;
164}
165
166static int
167sdmmc_parse(const char *fname, int *part, char **pfile)
168{
169	const char *full_path;
170	char pathbuf[PATH_MAX];
171
172	if (strchr(fname, ':') == NULL) {
173		snprintf(pathbuf, sizeof(pathbuf), "%s%c:%s",
174		    DEFAULT_DEVICE, DEFAULT_PART + 'a', fname);
175		full_path = pathbuf;
176		*pfile = __UNCONST(fname);
177	} else {
178		full_path = fname;
179		*pfile = strchr(fname, ':') + 1;
180	}
181	if (*pfile[0] == '\0') {
182		*pfile = __UNCONST("/");
183	}
184
185	if (strncmp(full_path, DEFAULT_DEVICE, 3) != 0) {
186		return EINVAL;
187	}
188	if (full_path[3] < 'a' || full_path[3] >= 'a' + MAXPARTITIONS ||
189	    full_path[4] != ':') {
190		return EINVAL;
191	}
192	*part = full_path[3] - 'a';
193
194	return 0;
195}
196
197int
198sdmmc_open(struct open_file *f, ...)
199{
200	int error, n, part;
201	const char *fname;
202	va_list ap;
203	char **file;
204	char *path;
205
206	va_start(ap, f);
207	fname = va_arg(ap, const char *);
208	file = va_arg(ap, char **);
209	va_end(ap);
210
211	error = sdmmc_parse(fname, &part, &path);
212	if (error != 0) {
213		return error;
214	}
215
216	for (n = 0; n < ndevs; n++) {
217		if (strcmp(DEV_NAME(&devsw[n]), "sdmmc") == 0) {
218			f->f_dev = &devsw[n];
219			break;
220		}
221	}
222	if (n == ndevs) {
223		return ENXIO;
224	}
225
226	f->f_devdata = &sdmmc_disklabel.d_partitions[part];
227	*file = path;
228
229	return 0;
230}
231
232int
233sdmmc_close(struct open_file *f)
234{
235	return 0;
236}
237
238int
239sdmmc_strategy(void *devdata, int rw, daddr_t dblk, size_t size, void *buf,
240    size_t *rsize)
241{
242	struct partition *p = devdata;
243	int error;
244
245	if (rw != F_READ) {
246		return EROFS;
247	}
248	if ((size % SDMMC_BLOCK_SIZE) != 0) {
249		printf("I/O must be multiple of SDMMC_BLOCK_SIZE\n");
250		return EIO;
251	}
252
253	gpio_clear(GPIO_SLOT_LED);
254	error = miniipc_sdmmc_read(le32toh(p->p_offset) + dblk,
255	    size / SDMMC_BLOCK_SIZE, buf);
256	gpio_set(GPIO_SLOT_LED);
257	if (error == 0) {
258		*rsize = size;
259	}
260
261	return error;
262}
263