freebsd-skq/sys/dev/fdc/fdc_acpi.c
Nate Lawson 20819082dc Fix ACPI floppy enumeration for three types of divergent behavior.
* Some systems have _FDE and child floppy devices, but no _FDI.  This seems
  to be compatible with the standard.  Don't error out if there is no _FDI.
  Instead, continue on to the next device.  The normal fd probe will take
  care of this device.
* Some systems have _FDE but no child devices in AML.  For these, add a
  second pass that compares the results of _FDE to the presence of devices.
  If not present, add the missing device.
* Some BIOS authors didn't read the spec.  They use tape drive values for
  all fdc(4) devices.  Since this isn't grossly incompatible with the
  required boolean value, use them.  They also define the _FDE items as a
  package instead of buffer.  Regenerate the buffer from the package if it
  is present.

Tested by:	tjr, marcel
2004-07-28 22:35:41 +00:00

311 lines
8.5 KiB
C

/*-
* 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/fdcreg.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);
static void fdctl_wr_acpi(fdc_p fdc, u_int8_t v);
/* 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 void
fdctl_wr_acpi(fdc_p fdc, u_int8_t v)
{
bus_space_write_1(fdc->ctlt, fdc->ctlh, 0, v);
}
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;
int error, i, ic_type;
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->fdctl_wr = fdctl_wr_acpi;
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;
/* Check that the controller is working and get its type. */
error = fdc_initial_reset(sc);
if (error)
goto out;
if (fd_cmd(sc, 1, NE7CMD_VERSION, 1, &ic_type) == 0) {
ic_type = (u_char)ic_type;
switch (ic_type) {
case 0x80:
sc->fdct = FDC_NE765;
break;
case 0x81: /* not mentioned in any hardware doc */
case 0x90:
sc->fdct = FDC_ENHANCED;
break;
default:
sc->fdct = FDC_UNKNOWN;
break;
}
}
/*
* 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))) {
/*
* 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 = (uint32_t *)buf.Pointer;
pkg = (ACPI_OBJECT *)buf.Pointer;
if (pkg->Type == ACPI_TYPE_PACKAGE) {
fde = malloc(pkg->Package.Count * sizeof(uint32_t),
M_TEMP, M_NOWAIT | M_ZERO);
if (fde == NULL) {
error = ENOMEM;
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;
}
}
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;
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;
*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. */
status = ACPI_EVALUATE_OBJECT(ctx->acpi_dev, *dev, "_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);