11.1Sjmcneill/* $NetBSD: sdmmc.c,v 1.1 2025/11/16 20:11:47 jmcneill Exp $ */
21.1Sjmcneill
31.1Sjmcneill/*-
41.1Sjmcneill * Copyright (c) 2025 Jared McNeill <jmcneill@invisible.ca>
51.1Sjmcneill * All rights reserved.
61.1Sjmcneill *
71.1Sjmcneill * Redistribution and use in source and binary forms, with or without
81.1Sjmcneill * modification, are permitted provided that the following conditions
91.1Sjmcneill * are met:
101.1Sjmcneill * 1. Redistributions of source code must retain the above copyright
111.1Sjmcneill *    notice, this list of conditions and the following disclaimer.
121.1Sjmcneill * 2. Redistributions in binary form must reproduce the above copyright
131.1Sjmcneill *    notice, this list of conditions and the following disclaimer in the
141.1Sjmcneill *    documentation and/or other materials provided with the distribution.
151.1Sjmcneill *
161.1Sjmcneill * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
171.1Sjmcneill * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
181.1Sjmcneill * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
191.1Sjmcneill * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
201.1Sjmcneill * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
211.1Sjmcneill * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
221.1Sjmcneill * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
231.1Sjmcneill * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
241.1Sjmcneill * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
251.1Sjmcneill * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
261.1Sjmcneill * SUCH DAMAGE.
271.1Sjmcneill */
281.1Sjmcneill
291.1Sjmcneill#include <lib/libsa/stand.h>
301.1Sjmcneill#include <lib/libkern/libkern.h>
311.1Sjmcneill
321.1Sjmcneill#include <sys/bootblock.h>
331.1Sjmcneill
341.1Sjmcneill#define FSTYPENAMES
351.1Sjmcneill#include <sys/disklabel.h>
361.1Sjmcneill
371.1Sjmcneill#include "sdmmc.h"
381.1Sjmcneill#include "miniipc.h"
391.1Sjmcneill#include "gpio.h"
401.1Sjmcneill
411.1Sjmcneill#if LABELSECTOR != 1
421.1Sjmcneill#error bad LABELSECTOR
431.1Sjmcneill#endif
441.1Sjmcneill
451.1Sjmcneill#define DEFAULT_DEVICE	"ld0"
461.1Sjmcneill#define DEFAULT_PART	0
471.1Sjmcneill
481.1Sjmcneill#define	SDMMC_STATE_NEW_CARD		2
491.1Sjmcneill
501.1Sjmcneillstatic uint8_t sdmmc_buf[SDMMC_BLOCK_SIZE];
511.1Sjmcneillstatic struct disklabel sdmmc_disklabel;
521.1Sjmcneill
531.1Sjmcneillstatic void
541.1Sjmcneillsdmmc_print_label(void)
551.1Sjmcneill{
561.1Sjmcneill#ifdef SDMMC_DEBUG
571.1Sjmcneill	struct disklabel *d = &sdmmc_disklabel;
581.1Sjmcneill	int part;
591.1Sjmcneill
601.1Sjmcneill	for (part = 0; part < le16toh(d->d_npartitions); part++) {
611.1Sjmcneill		struct partition *p = &d->d_partitions[part];
621.1Sjmcneill
631.1Sjmcneill		printf("partition %s%c ", DEFAULT_DEVICE, part + 'a');
641.1Sjmcneill
651.1Sjmcneill		if (p->p_fstype < __arraycount(fstypenames)) {
661.1Sjmcneill			printf("(%s)", fstypenames[p->p_fstype]);
671.1Sjmcneill		} else {
681.1Sjmcneill			printf("(type %u)", p->p_fstype);
691.1Sjmcneill		}
701.1Sjmcneill
711.1Sjmcneill		printf(" - %u %u\n", le32toh(p->p_offset), le32toh(p->p_size));
721.1Sjmcneill	}
731.1Sjmcneill#endif
741.1Sjmcneill}
751.1Sjmcneill
761.1Sjmcneillstatic int
771.1Sjmcneillsdmmc_read_label(daddr_t dblk, size_t size)
781.1Sjmcneill{
791.1Sjmcneill	struct disklabel d;
801.1Sjmcneill	int error;
811.1Sjmcneill
821.1Sjmcneill	error = miniipc_sdmmc_read(dblk + LABELSECTOR, 1, sdmmc_buf);
831.1Sjmcneill	if (error != 0) {
841.1Sjmcneill		printf("SDMMC: failed to read disklabel: %d\n", error);
851.1Sjmcneill		return error;
861.1Sjmcneill	}
871.1Sjmcneill	memcpy(&d, sdmmc_buf, sizeof(d));
881.1Sjmcneill
891.1Sjmcneill	if (le32toh(d.d_magic) != DISKMAGIC ||
901.1Sjmcneill	    le32toh(d.d_magic2) != DISKMAGIC) {
911.1Sjmcneill#ifdef SDMMC_DEBUG
921.1Sjmcneill		printf("SDMMC: bad diskmagic 0x%x 0x%x\n", le32toh(d.d_magic),
931.1Sjmcneill		    le32toh(d.d_magic2));
941.1Sjmcneill#endif
951.1Sjmcneill		return EINVAL;
961.1Sjmcneill	}
971.1Sjmcneill	if (le16toh(d.d_npartitions) > MAXPARTITIONS) {
981.1Sjmcneill		printf("SDMMC: bad npartitions %u\n", le16toh(d.d_npartitions));
991.1Sjmcneill		return EINVAL;
1001.1Sjmcneill	}
1011.1Sjmcneill
1021.1Sjmcneill	memcpy(&sdmmc_disklabel, sdmmc_buf, sizeof(sdmmc_disklabel));
1031.1Sjmcneill	return 0;
1041.1Sjmcneill}
1051.1Sjmcneill
1061.1Sjmcneillint
1071.1Sjmcneillsdmmc_init(void)
1081.1Sjmcneill{
1091.1Sjmcneill	struct mbr_sector mbr;
1101.1Sjmcneill	struct mbr_partition *mbr_part;
1111.1Sjmcneill	int error, n;
1121.1Sjmcneill	bool has_label = false;
1131.1Sjmcneill	uint32_t state, ack;
1141.1Sjmcneill
1151.1Sjmcneill	miniipc_sdmmc_state(&state);
1161.1Sjmcneill	if (state == SDMMC_STATE_NEW_CARD) {
1171.1Sjmcneill		miniipc_sdmmc_ack(&ack);
1181.1Sjmcneill	}
1191.1Sjmcneill
1201.1Sjmcneill	error = miniipc_sdmmc_read(0, 1, sdmmc_buf);
1211.1Sjmcneill	if (error != 0) {
1221.1Sjmcneill		printf("SDMMC: failed to read MBR: %d\n", error);
1231.1Sjmcneill		return error;
1241.1Sjmcneill	}
1251.1Sjmcneill	memcpy(&mbr, sdmmc_buf, sizeof(mbr));
1261.1Sjmcneill	if (le16toh(mbr.mbr_magic) != MBR_MAGIC) {
1271.1Sjmcneill		printf("SDMMC: bad MBR magic: 0x%x\n", le32toh(mbr.mbr_magic));
1281.1Sjmcneill		return ENXIO;
1291.1Sjmcneill	}
1301.1Sjmcneill
1311.1Sjmcneill	for (n = 0; n < MBR_PART_COUNT; n++) {
1321.1Sjmcneill		uint32_t start, size;
1331.1Sjmcneill
1341.1Sjmcneill		mbr_part = &mbr.mbr_parts[n];
1351.1Sjmcneill		size = le32toh(mbr_part->mbrp_size);
1361.1Sjmcneill		if (le32toh(mbr_part->mbrp_size) == 0) {
1371.1Sjmcneill			continue;
1381.1Sjmcneill		}
1391.1Sjmcneill		start = le32toh(mbr_part->mbrp_start);
1401.1Sjmcneill#ifdef SDMMC_DEBUG
1411.1Sjmcneill		printf("MBR part %u type 0x%x start %u size %u\n",
1421.1Sjmcneill		    n, mbr_part->mbrp_type, start, size);
1431.1Sjmcneill#endif
1441.1Sjmcneill
1451.1Sjmcneill		if (mbr_part->mbrp_type == MBR_PTYPE_NETBSD && !has_label) {
1461.1Sjmcneill			error = sdmmc_read_label(start, size);
1471.1Sjmcneill			if (error != 0) {
1481.1Sjmcneill				struct partition *p =
1491.1Sjmcneill				    &sdmmc_disklabel.d_partitions[0];
1501.1Sjmcneill
1511.1Sjmcneill				/* No label on disk, fake one. */
1521.1Sjmcneill				p->p_fstype = FS_BSDFFS;
1531.1Sjmcneill				p->p_size = htole32(size);
1541.1Sjmcneill				p->p_offset = htole32(start);
1551.1Sjmcneill				sdmmc_disklabel.d_npartitions = htole16(1);
1561.1Sjmcneill			}
1571.1Sjmcneill			has_label = true;
1581.1Sjmcneill		}
1591.1Sjmcneill	}
1601.1Sjmcneill
1611.1Sjmcneill	sdmmc_print_label();
1621.1Sjmcneill
1631.1Sjmcneill	return 0;
1641.1Sjmcneill}
1651.1Sjmcneill
1661.1Sjmcneillstatic int
1671.1Sjmcneillsdmmc_parse(const char *fname, int *part, char **pfile)
1681.1Sjmcneill{
1691.1Sjmcneill	const char *full_path;
1701.1Sjmcneill	char pathbuf[PATH_MAX];
1711.1Sjmcneill
1721.1Sjmcneill	if (strchr(fname, ':') == NULL) {
1731.1Sjmcneill		snprintf(pathbuf, sizeof(pathbuf), "%s%c:%s",
1741.1Sjmcneill		    DEFAULT_DEVICE, DEFAULT_PART + 'a', fname);
1751.1Sjmcneill		full_path = pathbuf;
1761.1Sjmcneill		*pfile = __UNCONST(fname);
1771.1Sjmcneill	} else {
1781.1Sjmcneill		full_path = fname;
1791.1Sjmcneill		*pfile = strchr(fname, ':') + 1;
1801.1Sjmcneill	}
1811.1Sjmcneill	if (*pfile[0] == '\0') {
1821.1Sjmcneill		*pfile = __UNCONST("/");
1831.1Sjmcneill	}
1841.1Sjmcneill
1851.1Sjmcneill	if (strncmp(full_path, DEFAULT_DEVICE, 3) != 0) {
1861.1Sjmcneill		return EINVAL;
1871.1Sjmcneill	}
1881.1Sjmcneill	if (full_path[3] < 'a' || full_path[3] >= 'a' + MAXPARTITIONS ||
1891.1Sjmcneill	    full_path[4] != ':') {
1901.1Sjmcneill		return EINVAL;
1911.1Sjmcneill	}
1921.1Sjmcneill	*part = full_path[3] - 'a';
1931.1Sjmcneill
1941.1Sjmcneill	return 0;
1951.1Sjmcneill}
1961.1Sjmcneill
1971.1Sjmcneillint
1981.1Sjmcneillsdmmc_open(struct open_file *f, ...)
1991.1Sjmcneill{
2001.1Sjmcneill	int error, n, part;
2011.1Sjmcneill	const char *fname;
2021.1Sjmcneill	va_list ap;
2031.1Sjmcneill	char **file;
2041.1Sjmcneill	char *path;
2051.1Sjmcneill
2061.1Sjmcneill	va_start(ap, f);
2071.1Sjmcneill	fname = va_arg(ap, const char *);
2081.1Sjmcneill	file = va_arg(ap, char **);
2091.1Sjmcneill	va_end(ap);
2101.1Sjmcneill
2111.1Sjmcneill	error = sdmmc_parse(fname, &part, &path);
2121.1Sjmcneill	if (error != 0) {
2131.1Sjmcneill		return error;
2141.1Sjmcneill	}
2151.1Sjmcneill
2161.1Sjmcneill	for (n = 0; n < ndevs; n++) {
2171.1Sjmcneill		if (strcmp(DEV_NAME(&devsw[n]), "sdmmc") == 0) {
2181.1Sjmcneill			f->f_dev = &devsw[n];
2191.1Sjmcneill			break;
2201.1Sjmcneill		}
2211.1Sjmcneill	}
2221.1Sjmcneill	if (n == ndevs) {
2231.1Sjmcneill		return ENXIO;
2241.1Sjmcneill	}
2251.1Sjmcneill
2261.1Sjmcneill	f->f_devdata = &sdmmc_disklabel.d_partitions[part];
2271.1Sjmcneill	*file = path;
2281.1Sjmcneill
2291.1Sjmcneill	return 0;
2301.1Sjmcneill}
2311.1Sjmcneill
2321.1Sjmcneillint
2331.1Sjmcneillsdmmc_close(struct open_file *f)
2341.1Sjmcneill{
2351.1Sjmcneill	return 0;
2361.1Sjmcneill}
2371.1Sjmcneill
2381.1Sjmcneillint
2391.1Sjmcneillsdmmc_strategy(void *devdata, int rw, daddr_t dblk, size_t size, void *buf,
2401.1Sjmcneill    size_t *rsize)
2411.1Sjmcneill{
2421.1Sjmcneill	struct partition *p = devdata;
2431.1Sjmcneill	int error;
2441.1Sjmcneill
2451.1Sjmcneill	if (rw != F_READ) {
2461.1Sjmcneill		return EROFS;
2471.1Sjmcneill	}
2481.1Sjmcneill	if ((size % SDMMC_BLOCK_SIZE) != 0) {
2491.1Sjmcneill		printf("I/O must be multiple of SDMMC_BLOCK_SIZE\n");
2501.1Sjmcneill		return EIO;
2511.1Sjmcneill	}
2521.1Sjmcneill
2531.1Sjmcneill	gpio_clear(GPIO_SLOT_LED);
2541.1Sjmcneill	error = miniipc_sdmmc_read(le32toh(p->p_offset) + dblk,
2551.1Sjmcneill	    size / SDMMC_BLOCK_SIZE, buf);
2561.1Sjmcneill	gpio_set(GPIO_SLOT_LED);
2571.1Sjmcneill	if (error == 0) {
2581.1Sjmcneill		*rsize = size;
2591.1Sjmcneill	}
2601.1Sjmcneill
2611.1Sjmcneill	return error;
2621.1Sjmcneill}
263