freebsd-dev/sys/dev/fdc/fdc_acpi.c

303 lines
8.4 KiB
C
Raw Normal View History

/*-
* Copyright (c) 2004 Nate Lawson (SDG)
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/kernel.h>
#include <sys/bio.h>
#include <sys/bus.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
#include "acpi.h"
#include <dev/acpica/acpivar.h>
#include <dev/fdc/fdcvar.h>
static int fdc_acpi_probe(device_t dev);
static int fdc_acpi_attach(device_t dev);
static int fdc_acpi_probe_children(device_t bus, device_t dev,
void *fde);
static ACPI_STATUS fdc_acpi_probe_child(ACPI_HANDLE h, device_t *dev,
int level, void *arg);
/* Maximum number of child devices of a controller (4 floppy + 1 tape.) */
#define ACPI_FDC_MAXDEVS 5
/*
* Parameters for the tape drive (5th device). Some BIOS authors use this
* for all drives, not just the tape drive (e.g., ASUS K8V). This isn't
* grossly incompatible with the spec since it says the first four devices
* are simple booleans.
*/
#define ACPI_FD_UNKNOWN 0
#define ACPI_FD_PRESENT 1
#define ACPI_FD_NEVER_PRESENT 2
/* Temporary buf length for evaluating _FDE and _FDI. */
#define ACPI_FDC_BUFLEN 1024
/* Context for walking FDC child devices. */
struct fdc_walk_ctx {
uint32_t fd_present[ACPI_FDC_MAXDEVS];
int index;
device_t acpi_dev;
device_t dev;
};
static int
fdc_acpi_probe(device_t dev)
{
device_t bus;
static char *fdc_ids[] = { "PNP0700", "PNP0701", NULL };
bus = device_get_parent(dev);
if (ACPI_ID_PROBE(bus, dev, fdc_ids) == NULL)
return (ENXIO);
if (ACPI_SUCCESS(ACPI_EVALUATE_OBJECT(bus, dev, "_FDE", NULL, NULL)))
device_set_desc(dev, "floppy drive controller (FDE)");
else
device_set_desc(dev, "floppy drive controller");
return (0);
}
static int
fdc_acpi_attach(device_t dev)
{
struct fdc_data *sc;
ACPI_BUFFER buf;
device_t bus;
Rewrite of the floppy driver to make it MPsafe & GEOM friendly: Centralize the fdctl_wr() function by adding the offset in the resource to the softc structure. Bugfix: Read the drive-change signal from the correct place: same place as the ctl register. Remove the cdevsw{} related code and implement a GEOM class. Ditch the state-engine and park a thread on each controller to service the queue. Make the interrupt FAST & MPSAFE since it is just a simple wakeup(9) call. Rely on a per controller mutex to protect the bioqueues. Grab GEOMs topology lock when we have to and Giant when ISADMA needs it. Since all access to the hardware is isolated in the per controller thread, the rest of the driver is lock & Giant free. Create a per-drive queue where requests are parked while the motor spins up. When the motor is running the requests are purged to the per controller queue. This allows requests to other drives to be serviced during spin-up. Only setup the motor-off timeout when we finish the last request on the queue and cancel it when a new request arrives. This fixes the bug in the old code where the motor turned off while we were still retrying a request. Make the "drive-change" work reliably. Probe the drive on first opens. Probe with a recal and a seek to cyl=1 to reset the drive change line and check again to see if we have a media. When we see the media disappear we destroy the geom provider, create a new one, and flag that autodetection should happen next time we see a media (unless a specific format is configured). Add sysctl tunables for a lot of drive related parameters. If you spend a lot of time waiting for floppies you can grab the i82078 pdf from Intels web-page and try tuning these. Add sysctl debug.fdc.debugflags which will enable various kinds of debugging printfs. Add central definitions of our well known floppy formats. Simplify datastructures for autoselection of format and call the code at the right times. Bugfix: Remove at least one piece of code which would have made 2.88M floppies not work. Use implied seeks on enhanced controllers. Use multisector transfers on all controllers. Increase ISADMA bounce buffers accordingly. Fall back to single sector when retrying. Reset retry count on every successful transaction. Sort functions in a more sensible order and generally tidy up a fair bit here and there. Assorted related fixes and adjustments in userland utilities. WORKAROUNDS: Do allow r/w opens of r/o media but refuse actual write operations. This is necessary until the p4::phk_bufwork branch gets integrated (This problem relates to remounting not reopening devices, see sys/*/*/${fs}_vfsops.c for details). Keep PC98's private copy of the old floppy driver compiling and presumably working (see below). TODO (planned) Move probing of drives until after interrupts/timeouts work (like for ATA/SCSI drives). TODO (unplanned) This driver should be made to work on PC98 as well. Test on YE-DATA PCMCIA floppy drive. Fix 2.88M media. This is a MT5 candidate (depends on the bioq_takefirst() addition).
2004-08-20 15:14:25 +00:00
int error, i;
ACPI_OBJECT *obj, *pkg;
ACPI_HANDLE h;
uint32_t *fde;
/* Get our softc and use the same accessor as ISA. */
sc = device_get_softc(dev);
sc->fdc_dev = dev;
sc->flags |= FDC_ISPNP;
/* Initialize variables and get a temporary buffer for _FDE. */
error = ENXIO;
h = acpi_get_handle(dev);
buf.Length = ACPI_FDC_BUFLEN;
buf.Pointer = malloc(buf.Length, M_TEMP, M_NOWAIT | M_ZERO);
if (buf.Pointer == NULL)
goto out;
/* Allocate resources the same as the ISA attachment. */
error = fdc_isa_alloc_resources(dev, sc);
if (error != 0)
goto out;
/* Call common attach code in fdc(4) first. */
error = fdc_attach(dev);
if (error != 0)
goto out;
/*
* Enumerate _FDE, which lists floppy drives that are present. If
* this fails, fall back to the ISA hints-based probe method.
*/
bus = device_get_parent(dev);
if (ACPI_SUCCESS(ACPI_EVALUATE_OBJECT(bus, dev, "_FDE", NULL, &buf))) {
obj = pkg = (ACPI_OBJECT *)buf.Pointer;
switch (obj->Type) {
case ACPI_TYPE_BUFFER:
/*
* The spec says _FDE should be a buffer of five
* 32-bit integers.
*/
fde = (uint32_t *)obj->Buffer.Pointer;
if (obj->Buffer.Length < 20) {
device_printf(dev, "_FDE too small\n");
goto out;
}
break;
case ACPI_TYPE_PACKAGE:
/*
* In violation of the spec, systems including the ASUS
* K8V return a package of five integers instead of a
* buffer of five 32-bit integers.
*/
fde = malloc(pkg->Package.Count * sizeof(uint32_t),
M_TEMP, M_NOWAIT | M_ZERO);
if (fde == NULL) {
goto out;
}
for (i = 0; i < pkg->Package.Count; i++) {
obj = &pkg->Package.Elements[i];
if (obj->Type == ACPI_TYPE_INTEGER)
fde[i] = (uint32_t)obj->Integer.Value;
}
break;
default:
device_printf(dev, "invalid _FDE type %d\n", obj->Type);
goto out;
}
error = fdc_acpi_probe_children(bus, dev, fde);
if (pkg->Type == ACPI_TYPE_PACKAGE)
free(fde, M_TEMP);
} else
error = fdc_hints_probe(dev);
out:
if (buf.Pointer)
free(buf.Pointer, M_TEMP);
if (error != 0)
fdc_release_resources(sc);
return (error);
}
static int
fdc_acpi_probe_children(device_t bus, device_t dev, void *fde)
{
struct fdc_walk_ctx *ctx;
devclass_t fd_dc;
int i;
/* Setup the context and walk all child devices. */
ctx = malloc(sizeof(struct fdc_walk_ctx), M_TEMP, M_NOWAIT);
if (ctx == NULL) {
device_printf(dev, "no memory for walking children\n");
return (ENOMEM);
}
bcopy(fde, ctx->fd_present, sizeof(ctx->fd_present));
ctx->index = 0;
ctx->dev = dev;
ctx->acpi_dev = bus;
ACPI_SCAN_CHILDREN(ctx->acpi_dev, dev, 1, fdc_acpi_probe_child,
ctx);
/* Add any devices not represented by an AML Device handle/node. */
fd_dc = devclass_find("fd");
for (i = 0; i < ACPI_FDC_MAXDEVS; i++)
if (ctx->fd_present[i] == ACPI_FD_PRESENT &&
devclass_get_device(fd_dc, i) == NULL) {
if (fdc_add_child(dev, "fd", i) == NULL)
device_printf(dev, "fd add failed\n");
}
free(ctx, M_TEMP);
/* Attach any children found during the probe. */
return (bus_generic_attach(dev));
}
static ACPI_STATUS
fdc_acpi_probe_child(ACPI_HANDLE h, device_t *dev, int level, void *arg)
{
struct fdc_walk_ctx *ctx;
device_t child, old_child;
ACPI_BUFFER buf;
ACPI_OBJECT *pkg, *obj;
ACPI_STATUS status;
ctx = (struct fdc_walk_ctx *)arg;
buf.Pointer = NULL;
/*
* The first four ints are booleans that indicate whether fd0-3 are
* present or not. The last is for a tape device, which we don't
* bother supporting for now.
*/
if (ctx->index > 3)
return (AE_OK);
/* This device is not present, move on to the next. */
if (ctx->fd_present[ctx->index] != ACPI_FD_PRESENT)
goto out;
/* Create a device for the child with the given index. */
child = fdc_add_child(ctx->dev, "fd", ctx->index);
if (child == NULL)
goto out;
old_child = *dev;
*dev = child;
/* Get temporary buffer for _FDI probe. */
buf.Length = ACPI_FDC_BUFLEN;
buf.Pointer = malloc(buf.Length, M_TEMP, M_NOWAIT | M_ZERO);
if (buf.Pointer == NULL)
goto out;
/*
* Evaluate _FDI to get drive type to pass to the child. We use the
* old child here since it has a valid ACPI_HANDLE since it is a
* child of acpi. A better way to implement this would be to make fdc
* support the ACPI handle ivar for its children.
*/
status = ACPI_EVALUATE_OBJECT(ctx->acpi_dev, old_child, "_FDI", NULL,
&buf);
if (ACPI_FAILURE(status)) {
if (status != AE_NOT_FOUND)
device_printf(ctx->dev, "_FDI failed - %#x\n", status);
goto out;
}
pkg = (ACPI_OBJECT *)buf.Pointer;
if (!ACPI_PKG_VALID(pkg, 16)) {
device_printf(ctx->dev, "invalid _FDI package\n");
goto out;
}
obj = &pkg->Package.Elements[1];
if (obj == NULL || obj->Type != ACPI_TYPE_INTEGER) {
device_printf(ctx->dev, "invalid type object in _FDI\n");
goto out;
}
fdc_set_fdtype(child, obj->Integer.Value);
out:
ctx->index++;
if (buf.Pointer)
free(buf.Pointer, M_TEMP);
return (AE_OK);
}
static device_method_t fdc_acpi_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, fdc_acpi_probe),
DEVMETHOD(device_attach, fdc_acpi_attach),
DEVMETHOD(device_detach, fdc_detach),
/* Bus interface */
DEVMETHOD(bus_print_child, fdc_print_child),
DEVMETHOD(bus_read_ivar, fdc_read_ivar),
DEVMETHOD(bus_write_ivar, fdc_write_ivar),
{0, 0}
};
static driver_t fdc_acpi_driver = {
"fdc",
fdc_acpi_methods,
sizeof(struct fdc_data)
};
DRIVER_MODULE(fdc, acpi, fdc_acpi_driver, fdc_devclass, 0, 0);