398d5fc6af
sdhci(4), mmc(4) and mmcsd(4). For the most part, this consists of: - Correcting and extending the infrastructure for negotiating and enabling post-DDR52 modes already added as part of r315598. In fact, HS400ES now should work as well but hasn't been activated due to lack of corresponding hardware. - Adding support executing standard SDHCI initial tuning as well as re-tuning as required for eMMC HS200/HS400 and the fast UHS-I SD card modes. Currently, corresponding methods are only hooked up to the ACPI and PCI front-ends of sdhci(4), though. Moreover, sdhci(4) won't offer any modes requiring (re-)tuning to the MMC/SD layer in order to not break operations with other sdhci(4) front- ends. Likewise, sdhci(4) now no longer offers modes requiring the set_uhs_timing method introduced in r315598 to be implemented/ hooked up (previously, this method was used with DDR52 only, which in turn is only available with Intel controllers so far, i. e. no such limitation was necessary before). Similarly for 1.2/1.8 V VCCQ support and the switch_vccq method. - Addition of locking to the IOCTL half of mmcsd(4) to prevent races with detachment and suspension, especially since it's required to immediately switch away from RPMB partitions again after an access to these (so re-tuning can take place anew, given that the current eMMC specification v5.1 doesn't allow tuning commands to be issued with a RPMB partition selected). Therefore, the existing part_mtx lock in the mmcsd(4) softc is additionally renamed to disk_mtx in order to denote that it only refers to the disk(9) half, likewise for corresponding macros. On the system where the addition of DDR52 support increased the read throughput to ~80 MB/s (from ~45 MB/s at high speed), HS200 yields ~154 MB/s and HS400 ~187 MB/s, i. e. performance now has more than quadrupled compared to pre-r315598. Also, with the advent of (re-)tuning support, most infrastructure necessary for SD card UHS-I modes up to SDR104 now is also in place. Note, though, that the standard SDHCI way of (re-)tuning is special in several ways, which also is why sending the actual tuning requests to the device is part of sdhci(4). SDHCI implementations not following the specification, MMC and non-SDHCI SD card controllers likely will use a generic implementation in the MMC/SD layer for executing tuning, which hasn't been written so far, though. However, in fact this isn't a feature-only change; there are boards based on Intel Bay Trail where DDR52 is problematic and the suggested workaround is to use HS200 mode instead. So far exact details are unknown, however, i. e. whether that's due to a defect in these SoCs or on the boards. Moreover, due to the above changes requiring to be aware of possible MMC siblings in the fast path of mmc(4), corresponding information now is cached in mmc_softc. As a side-effect, mmc_calculate_clock(), mmc_delete_cards(), mmc_discover_cards() and mmc_rescan_cards() now all are guaranteed to operate on the same set of devices as there no longer is any use of device_get_children(9), which can fail in low memory situations. Likewise, mmc_calculate_clock() now longer will trigger a panic due to the latter. o Fix a bug in the failure reporting of mmcsd_delete(); in case of an error when the starting block of a previously stored erase request is used (in order to be able to erase a full erase sector worth of data), the starting block of the newly supplied bio_pblkno has to be returned for indicating no progress. Otherwise, upper layers might be told that a negative number of BIOs have been completed, leading to a panic. o Fix 2 bugs on resume: - Things done in fork1(9) like the acquisition of an SX lock or the sleepable memory allocation are incompatible with a MTX_DEF taken. Thus, mmcsd_resume() must not call kproc_create(9), which in turn uses fork1(9), with the disk_mtx (formerly part_mtx) held. - In mmc_suspend(), the bus is powered down, which in the typical case of a device being selected at the time of suspension, causes the device deselection as part of the bus acquisition by mmc(4) in mmc_scan() to fail as the bus isn't powered up again before later in mmc_go_discovery(). Thus, power down with the bus acquired in mmc_suspend(), which will trigger the deselection up-front. o Fix a memory leak in mmcsd_ioctl() in case copyin(9) fails. [1] o Fix missing variable initialization in mmc_switch_status(). [2] o Fix R1_SWITCH_ERROR detection in mmc_switch_status(). [3] o Handle the case of device_add_child(9) failing, for example due to a memory shortage, gracefully in mmc(4) and sdhci(4), including not leaking memory for the instance variables in case of mmc(4) (which might or might not fix [4] as the latter problem has been discovered independently). o Handle the case of an unknown SD CSD version in mmc_decode_csd_sd() gracefully instead of calling panic(9). o Again, check and handle the return values of some additional function calls in mmc(4) instead of assuming that everything went right or mark non-fatal errors by casting the return value to void. o Correct a typo in the Linux IOCTL compatibility; it should have been MMC_IOC_MULTI_CMD rather than MMC_IOC_CMD_MULTI. o Now that we are reaching ever faster speeds (more improvement in this regard is to be expected when adding ADMA support to sdhci(4)), apply a few micro-optimizations like predicting mmc(4) and sdhci(4) debugging to be off or caching erase sector and maximum data sizes as well support of block addressing in mmsd(4) (instead of doing 2 indirections on every read/write request for determining the maximum data size for example). Reported by: Coverity CID: 1372612 [1], 1372624 [2], 1372594 [3], 1007069 [4]
409 lines
11 KiB
C
409 lines
11 KiB
C
/*-
|
|
* Copyright (c) 2017 Oleksandr Tymoshenko <gonzo@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/module.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/taskqueue.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/resource.h>
|
|
|
|
#include <contrib/dev/acpica/include/acpi.h>
|
|
#include <dev/acpica/acpivar.h>
|
|
|
|
#include <dev/mmc/bridge.h>
|
|
|
|
#include <dev/sdhci/sdhci.h>
|
|
|
|
#include "mmcbr_if.h"
|
|
#include "sdhci_if.h"
|
|
|
|
static const struct sdhci_acpi_device {
|
|
const char* hid;
|
|
int uid;
|
|
const char *desc;
|
|
u_int quirks;
|
|
} sdhci_acpi_devices[] = {
|
|
{ "80860F14", 1, "Intel Bay Trail/Braswell eMMC 4.5/4.5.1 Controller",
|
|
SDHCI_QUIRK_ALL_SLOTS_NON_REMOVABLE |
|
|
SDHCI_QUIRK_INTEL_POWER_UP_RESET |
|
|
SDHCI_QUIRK_WAIT_WHILE_BUSY |
|
|
SDHCI_QUIRK_MMC_DDR52 |
|
|
SDHCI_QUIRK_CAPS_BIT63_FOR_MMC_HS400 |
|
|
SDHCI_QUIRK_PRESET_VALUE_BROKEN },
|
|
{ "80860F14", 3, "Intel Bay Trail/Braswell SDXC Controller",
|
|
SDHCI_QUIRK_WAIT_WHILE_BUSY |
|
|
SDHCI_QUIRK_PRESET_VALUE_BROKEN },
|
|
{ "80860F16", 0, "Intel Bay Trail/Braswell SDXC Controller",
|
|
SDHCI_QUIRK_WAIT_WHILE_BUSY |
|
|
SDHCI_QUIRK_PRESET_VALUE_BROKEN },
|
|
{ "80865ACA", 0, "Intel Apollo Lake SDXC Controller",
|
|
SDHCI_QUIRK_BROKEN_DMA | /* APL18 erratum */
|
|
SDHCI_QUIRK_WAIT_WHILE_BUSY |
|
|
SDHCI_QUIRK_PRESET_VALUE_BROKEN },
|
|
{ "80865ACC", 0, "Intel Apollo Lake eMMC 5.0 Controller",
|
|
SDHCI_QUIRK_BROKEN_DMA | /* APL18 erratum */
|
|
SDHCI_QUIRK_ALL_SLOTS_NON_REMOVABLE |
|
|
SDHCI_QUIRK_INTEL_POWER_UP_RESET |
|
|
SDHCI_QUIRK_WAIT_WHILE_BUSY |
|
|
SDHCI_QUIRK_MMC_DDR52 |
|
|
SDHCI_QUIRK_CAPS_BIT63_FOR_MMC_HS400 |
|
|
SDHCI_QUIRK_PRESET_VALUE_BROKEN },
|
|
{ NULL, 0, NULL, 0}
|
|
};
|
|
|
|
static char *sdhci_ids[] = {
|
|
"80860F14",
|
|
"80860F16",
|
|
"80865ACA",
|
|
"80865ACC",
|
|
NULL
|
|
};
|
|
|
|
struct sdhci_acpi_softc {
|
|
u_int quirks; /* Chip specific quirks */
|
|
struct resource *irq_res; /* IRQ resource */
|
|
void *intrhand; /* Interrupt handle */
|
|
|
|
struct sdhci_slot slot;
|
|
struct resource *mem_res; /* Memory resource */
|
|
};
|
|
|
|
static void sdhci_acpi_intr(void *arg);
|
|
static int sdhci_acpi_detach(device_t dev);
|
|
|
|
static uint8_t
|
|
sdhci_acpi_read_1(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_barrier(sc->mem_res, 0, 0xFF,
|
|
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
|
|
return bus_read_1(sc->mem_res, off);
|
|
}
|
|
|
|
static void
|
|
sdhci_acpi_write_1(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off, uint8_t val)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_barrier(sc->mem_res, 0, 0xFF,
|
|
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
|
|
bus_write_1(sc->mem_res, off, val);
|
|
}
|
|
|
|
static uint16_t
|
|
sdhci_acpi_read_2(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_barrier(sc->mem_res, 0, 0xFF,
|
|
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
|
|
return bus_read_2(sc->mem_res, off);
|
|
}
|
|
|
|
static void
|
|
sdhci_acpi_write_2(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off, uint16_t val)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_barrier(sc->mem_res, 0, 0xFF,
|
|
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
|
|
bus_write_2(sc->mem_res, off, val);
|
|
}
|
|
|
|
static uint32_t
|
|
sdhci_acpi_read_4(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_barrier(sc->mem_res, 0, 0xFF,
|
|
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
|
|
return bus_read_4(sc->mem_res, off);
|
|
}
|
|
|
|
static void
|
|
sdhci_acpi_write_4(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off, uint32_t val)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_barrier(sc->mem_res, 0, 0xFF,
|
|
BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
|
|
bus_write_4(sc->mem_res, off, val);
|
|
}
|
|
|
|
static void
|
|
sdhci_acpi_read_multi_4(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off, uint32_t *data, bus_size_t count)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_read_multi_stream_4(sc->mem_res, off, data, count);
|
|
}
|
|
|
|
static void
|
|
sdhci_acpi_write_multi_4(device_t dev, struct sdhci_slot *slot __unused,
|
|
bus_size_t off, uint32_t *data, bus_size_t count)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
bus_write_multi_stream_4(sc->mem_res, off, data, count);
|
|
}
|
|
|
|
static const struct sdhci_acpi_device *
|
|
sdhci_acpi_find_device(device_t dev)
|
|
{
|
|
const char *hid;
|
|
int i, uid;
|
|
ACPI_HANDLE handle;
|
|
ACPI_STATUS status;
|
|
|
|
hid = ACPI_ID_PROBE(device_get_parent(dev), dev, sdhci_ids);
|
|
if (hid == NULL)
|
|
return (NULL);
|
|
|
|
handle = acpi_get_handle(dev);
|
|
status = acpi_GetInteger(handle, "_UID", &uid);
|
|
if (ACPI_FAILURE(status))
|
|
uid = 0;
|
|
|
|
for (i = 0; sdhci_acpi_devices[i].hid != NULL; i++) {
|
|
if (strcmp(sdhci_acpi_devices[i].hid, hid) != 0)
|
|
continue;
|
|
if ((sdhci_acpi_devices[i].uid != 0) &&
|
|
(sdhci_acpi_devices[i].uid != uid))
|
|
continue;
|
|
return (&sdhci_acpi_devices[i]);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
static int
|
|
sdhci_acpi_probe(device_t dev)
|
|
{
|
|
const struct sdhci_acpi_device *acpi_dev;
|
|
|
|
acpi_dev = sdhci_acpi_find_device(dev);
|
|
if (acpi_dev == NULL)
|
|
return (ENXIO);
|
|
|
|
device_set_desc(dev, acpi_dev->desc);
|
|
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
static int
|
|
sdhci_acpi_attach(device_t dev)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
int rid, err;
|
|
const struct sdhci_acpi_device *acpi_dev;
|
|
|
|
acpi_dev = sdhci_acpi_find_device(dev);
|
|
if (acpi_dev == NULL)
|
|
return (ENXIO);
|
|
|
|
sc->quirks = acpi_dev->quirks;
|
|
|
|
/* Allocate IRQ. */
|
|
rid = 0;
|
|
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
|
|
RF_ACTIVE);
|
|
if (sc->irq_res == NULL) {
|
|
device_printf(dev, "can't allocate IRQ\n");
|
|
return (ENOMEM);
|
|
}
|
|
|
|
rid = 0;
|
|
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
|
|
&rid, RF_ACTIVE);
|
|
if (sc->mem_res == NULL) {
|
|
device_printf(dev, "can't allocate memory resource for slot\n");
|
|
sdhci_acpi_detach(dev);
|
|
return (ENOMEM);
|
|
}
|
|
|
|
/* Intel Braswell eMMC 4.5.1 controller quirk */
|
|
if (strcmp(acpi_dev->hid, "80860F14") == 0 && acpi_dev->uid == 1 &&
|
|
SDHCI_READ_4(dev, &sc->slot, SDHCI_CAPABILITIES) == 0x446cc8b2 &&
|
|
SDHCI_READ_4(dev, &sc->slot, SDHCI_CAPABILITIES2) == 0x00000807)
|
|
sc->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_1MHZ;
|
|
sc->quirks &= ~sdhci_quirk_clear;
|
|
sc->quirks |= sdhci_quirk_set;
|
|
sc->slot.quirks = sc->quirks;
|
|
|
|
err = sdhci_init_slot(dev, &sc->slot, 0);
|
|
if (err) {
|
|
device_printf(dev, "failed to init slot\n");
|
|
sdhci_acpi_detach(dev);
|
|
return (err);
|
|
}
|
|
|
|
/* Activate the interrupt */
|
|
err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
|
|
NULL, sdhci_acpi_intr, sc, &sc->intrhand);
|
|
if (err) {
|
|
device_printf(dev, "can't setup IRQ\n");
|
|
sdhci_acpi_detach(dev);
|
|
return (err);
|
|
}
|
|
|
|
/* Process cards detection. */
|
|
sdhci_start_slot(&sc->slot);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sdhci_acpi_detach(device_t dev)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
|
|
if (sc->intrhand)
|
|
bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
|
|
if (sc->irq_res)
|
|
bus_release_resource(dev, SYS_RES_IRQ,
|
|
rman_get_rid(sc->irq_res), sc->irq_res);
|
|
|
|
if (sc->mem_res) {
|
|
sdhci_cleanup_slot(&sc->slot);
|
|
bus_release_resource(dev, SYS_RES_MEMORY,
|
|
rman_get_rid(sc->mem_res), sc->mem_res);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sdhci_acpi_shutdown(device_t dev)
|
|
{
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sdhci_acpi_suspend(device_t dev)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
int err;
|
|
|
|
err = bus_generic_suspend(dev);
|
|
if (err)
|
|
return (err);
|
|
sdhci_generic_suspend(&sc->slot);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
sdhci_acpi_resume(device_t dev)
|
|
{
|
|
struct sdhci_acpi_softc *sc = device_get_softc(dev);
|
|
int err;
|
|
|
|
sdhci_generic_resume(&sc->slot);
|
|
err = bus_generic_resume(dev);
|
|
if (err)
|
|
return (err);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
sdhci_acpi_intr(void *arg)
|
|
{
|
|
struct sdhci_acpi_softc *sc = (struct sdhci_acpi_softc *)arg;
|
|
|
|
sdhci_generic_intr(&sc->slot);
|
|
}
|
|
|
|
static device_method_t sdhci_methods[] = {
|
|
/* device_if */
|
|
DEVMETHOD(device_probe, sdhci_acpi_probe),
|
|
DEVMETHOD(device_attach, sdhci_acpi_attach),
|
|
DEVMETHOD(device_detach, sdhci_acpi_detach),
|
|
DEVMETHOD(device_shutdown, sdhci_acpi_shutdown),
|
|
DEVMETHOD(device_suspend, sdhci_acpi_suspend),
|
|
DEVMETHOD(device_resume, sdhci_acpi_resume),
|
|
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_read_ivar, sdhci_generic_read_ivar),
|
|
DEVMETHOD(bus_write_ivar, sdhci_generic_write_ivar),
|
|
|
|
/* mmcbr_if */
|
|
DEVMETHOD(mmcbr_update_ios, sdhci_generic_update_ios),
|
|
DEVMETHOD(mmcbr_switch_vccq, sdhci_generic_switch_vccq),
|
|
DEVMETHOD(mmcbr_tune, sdhci_generic_tune),
|
|
DEVMETHOD(mmcbr_retune, sdhci_generic_retune),
|
|
DEVMETHOD(mmcbr_request, sdhci_generic_request),
|
|
DEVMETHOD(mmcbr_get_ro, sdhci_generic_get_ro),
|
|
DEVMETHOD(mmcbr_acquire_host, sdhci_generic_acquire_host),
|
|
DEVMETHOD(mmcbr_release_host, sdhci_generic_release_host),
|
|
|
|
/* SDHCI accessors */
|
|
DEVMETHOD(sdhci_read_1, sdhci_acpi_read_1),
|
|
DEVMETHOD(sdhci_read_2, sdhci_acpi_read_2),
|
|
DEVMETHOD(sdhci_read_4, sdhci_acpi_read_4),
|
|
DEVMETHOD(sdhci_read_multi_4, sdhci_acpi_read_multi_4),
|
|
DEVMETHOD(sdhci_write_1, sdhci_acpi_write_1),
|
|
DEVMETHOD(sdhci_write_2, sdhci_acpi_write_2),
|
|
DEVMETHOD(sdhci_write_4, sdhci_acpi_write_4),
|
|
DEVMETHOD(sdhci_write_multi_4, sdhci_acpi_write_multi_4),
|
|
DEVMETHOD(sdhci_set_uhs_timing, sdhci_generic_set_uhs_timing),
|
|
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t sdhci_acpi_driver = {
|
|
"sdhci_acpi",
|
|
sdhci_methods,
|
|
sizeof(struct sdhci_acpi_softc),
|
|
};
|
|
static devclass_t sdhci_acpi_devclass;
|
|
|
|
DRIVER_MODULE(sdhci_acpi, acpi, sdhci_acpi_driver, sdhci_acpi_devclass, NULL,
|
|
NULL);
|
|
MODULE_DEPEND(sdhci_acpi, sdhci, 1, 1, 1);
|
|
|
|
#ifndef MMCCAM
|
|
MMC_DECLARE_BRIDGE(sdhci_acpi);
|
|
#endif
|