2014-04-26 03:54:58 +04:00
|
|
|
|
/* $NetBSD: apple_smc.c,v 1.6 2014/04/25 23:54:59 riastradh Exp $ */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Apple System Management Controller
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*-
|
|
|
|
|
* Copyright (c) 2013 The NetBSD Foundation, Inc.
|
|
|
|
|
* All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
|
|
|
* by Taylor R. Campbell.
|
|
|
|
|
*
|
|
|
|
|
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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>
|
2014-04-26 03:54:58 +04:00
|
|
|
|
__KERNEL_RCSID(0, "$NetBSD: apple_smc.c,v 1.6 2014/04/25 23:54:59 riastradh Exp $");
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
|
#include <sys/device.h>
|
|
|
|
|
#include <sys/errno.h>
|
|
|
|
|
#include <sys/kmem.h>
|
2014-04-01 21:48:39 +04:00
|
|
|
|
#include <sys/module.h>
|
2014-04-01 21:47:36 +04:00
|
|
|
|
#include <sys/mutex.h>
|
2014-04-01 21:48:39 +04:00
|
|
|
|
#include <sys/rwlock.h>
|
2014-04-01 21:47:36 +04:00
|
|
|
|
#if 0 /* XXX sysctl */
|
|
|
|
|
#include <sys/sysctl.h>
|
|
|
|
|
#endif
|
|
|
|
|
#include <sys/systm.h>
|
|
|
|
|
|
|
|
|
|
#include <dev/ic/apple_smc.h>
|
|
|
|
|
#include <dev/ic/apple_smcreg.h>
|
|
|
|
|
#include <dev/ic/apple_smcvar.h>
|
|
|
|
|
|
2014-04-01 21:48:52 +04:00
|
|
|
|
/* Must match the config(5) name. */
|
2014-04-01 21:48:39 +04:00
|
|
|
|
#define APPLE_SMC_BUS "applesmcbus"
|
|
|
|
|
|
2014-04-01 21:48:52 +04:00
|
|
|
|
static int apple_smc_search(device_t, cfdata_t, const int *, void *);
|
2014-04-01 21:47:36 +04:00
|
|
|
|
static uint8_t apple_smc_bus_read_1(struct apple_smc_tag *, bus_size_t);
|
|
|
|
|
static void apple_smc_bus_write_1(struct apple_smc_tag *, bus_size_t,
|
|
|
|
|
uint8_t);
|
|
|
|
|
static int apple_smc_read_data(struct apple_smc_tag *, uint8_t *);
|
|
|
|
|
static int apple_smc_write(struct apple_smc_tag *, bus_size_t, uint8_t);
|
|
|
|
|
static int apple_smc_write_cmd(struct apple_smc_tag *, uint8_t);
|
|
|
|
|
static int apple_smc_write_data(struct apple_smc_tag *, uint8_t);
|
|
|
|
|
static int apple_smc_begin(struct apple_smc_tag *, uint8_t,
|
|
|
|
|
const char *, uint8_t);
|
|
|
|
|
static int apple_smc_input(struct apple_smc_tag *, uint8_t,
|
|
|
|
|
const char *, void *, uint8_t);
|
|
|
|
|
static int apple_smc_output(struct apple_smc_tag *, uint8_t,
|
|
|
|
|
const char *, const void *, uint8_t);
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apple_smc_attach(struct apple_smc_tag *smc)
|
|
|
|
|
{
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
mutex_init(&smc->smc_io_lock, MUTEX_DEFAULT, IPL_NONE);
|
2014-04-01 21:48:39 +04:00
|
|
|
|
#if 0 /* XXX sysctl */
|
|
|
|
|
apple_smc_sysctl_setup(smc);
|
|
|
|
|
#endif
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Attach any children. */
|
2014-04-01 21:48:52 +04:00
|
|
|
|
(void)apple_smc_rescan(smc, APPLE_SMC_BUS, NULL);
|
2014-04-01 21:47:36 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_detach(struct apple_smc_tag *smc, int flags)
|
|
|
|
|
{
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Fail if we can't detach all our children. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
error = config_detach_children(smc->smc_dev, flags);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
|
|
#if 0 /* XXX sysctl */
|
|
|
|
|
sysctl_teardown(&smc->smc_log);
|
|
|
|
|
#endif
|
2014-04-01 21:49:05 +04:00
|
|
|
|
mutex_destroy(&smc->smc_io_lock);
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-01 21:48:39 +04:00
|
|
|
|
int
|
|
|
|
|
apple_smc_rescan(struct apple_smc_tag *smc, const char *ifattr,
|
2014-04-01 21:48:52 +04:00
|
|
|
|
const int *locators)
|
2014-04-01 21:48:39 +04:00
|
|
|
|
{
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Let autoconf(9) do the work of finding new children. */
|
2014-04-01 21:48:52 +04:00
|
|
|
|
(void)config_search_loc(&apple_smc_search, smc->smc_dev, APPLE_SMC_BUS,
|
|
|
|
|
locators, smc);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
2014-04-01 21:48:52 +04:00
|
|
|
|
static int
|
|
|
|
|
apple_smc_search(device_t parent, cfdata_t cf, const int *locators, void *aux)
|
|
|
|
|
{
|
|
|
|
|
struct apple_smc_tag *const smc = aux;
|
|
|
|
|
static const struct apple_smc_attach_args zero_asa;
|
|
|
|
|
struct apple_smc_attach_args asa = zero_asa;
|
|
|
|
|
device_t dev;
|
|
|
|
|
deviter_t di;
|
|
|
|
|
bool attached = false;
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
|
|
|
|
/*
|
2014-04-01 21:48:52 +04:00
|
|
|
|
* If this device has already attached, don't attach it again.
|
2014-04-01 21:48:39 +04:00
|
|
|
|
*
|
2014-04-01 21:48:52 +04:00
|
|
|
|
* XXX This is a pretty silly way to query the children, but
|
|
|
|
|
* struct device doesn't seem to list its children.
|
2014-04-01 21:48:39 +04:00
|
|
|
|
*/
|
2014-04-01 21:48:52 +04:00
|
|
|
|
for (dev = deviter_first(&di, DEVITER_F_LEAVES_FIRST);
|
|
|
|
|
dev != NULL;
|
|
|
|
|
dev = deviter_next(&di)) {
|
|
|
|
|
if (device_parent(dev) != parent)
|
2014-04-01 21:48:39 +04:00
|
|
|
|
continue;
|
2014-04-01 21:48:52 +04:00
|
|
|
|
if (!device_is_a(dev, cf->cf_name))
|
2014-04-01 21:48:39 +04:00
|
|
|
|
continue;
|
2014-04-01 21:48:52 +04:00
|
|
|
|
attached = true;
|
|
|
|
|
break;
|
2014-04-01 21:48:39 +04:00
|
|
|
|
}
|
2014-04-01 21:48:52 +04:00
|
|
|
|
deviter_release(&di);
|
|
|
|
|
if (attached)
|
|
|
|
|
return 0;
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
2014-04-01 21:48:52 +04:00
|
|
|
|
/* If this device doesn't match, don't attach it. */
|
|
|
|
|
if (!config_match(parent, cf, aux))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/* Looks hunky-dory. Attach. */
|
|
|
|
|
asa.asa_smc = smc;
|
|
|
|
|
(void)config_attach_loc(parent, cf, locators, &asa, NULL);
|
|
|
|
|
return 0;
|
2014-04-01 21:48:39 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2014-04-01 21:48:52 +04:00
|
|
|
|
apple_smc_child_detached(struct apple_smc_tag *smc __unused,
|
|
|
|
|
device_t child __unused)
|
2014-04-01 21:48:39 +04:00
|
|
|
|
{
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* We keep no books about our children. */
|
2014-04-01 21:48:39 +04:00
|
|
|
|
}
|
|
|
|
|
|
2014-04-01 21:47:36 +04:00
|
|
|
|
static uint8_t
|
|
|
|
|
apple_smc_bus_read_1(struct apple_smc_tag *smc, bus_size_t reg)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return bus_space_read_1(smc->smc_bst, smc->smc_bsh, reg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
apple_smc_bus_write_1(struct apple_smc_tag *smc, bus_size_t reg, uint8_t v)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
bus_space_write_1(smc->smc_bst, smc->smc_bsh, reg, v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* XXX These delays are pretty randomly chosen. Wait in 100 us
|
|
|
|
|
* increments, up to a total of 1 ms.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
apple_smc_read_data(struct apple_smc_tag *smc, uint8_t *byte)
|
|
|
|
|
{
|
|
|
|
|
uint8_t status;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
KASSERT(mutex_owned(&smc->smc_io_lock));
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/*
|
|
|
|
|
* Wait until the status register says there's data to read and
|
|
|
|
|
* read it.
|
|
|
|
|
*/
|
2014-04-01 21:47:36 +04:00
|
|
|
|
for (i = 0; i < 100; i++) {
|
|
|
|
|
status = apple_smc_bus_read_1(smc, APPLE_SMC_CSR);
|
|
|
|
|
if (status & APPLE_SMC_STATUS_READ_READY) {
|
|
|
|
|
*byte = apple_smc_bus_read_1(smc, APPLE_SMC_DATA);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
DELAY(100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ETIMEDOUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
apple_smc_write(struct apple_smc_tag *smc, bus_size_t reg, uint8_t byte)
|
|
|
|
|
{
|
|
|
|
|
uint8_t status;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
KASSERT(mutex_owned(&smc->smc_io_lock));
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/*
|
|
|
|
|
* Write the byte and then wait until the status register says
|
|
|
|
|
* it has been accepted.
|
|
|
|
|
*/
|
2014-04-01 21:47:36 +04:00
|
|
|
|
apple_smc_bus_write_1(smc, reg, byte);
|
|
|
|
|
for (i = 0; i < 100; i++) {
|
|
|
|
|
status = apple_smc_bus_read_1(smc, APPLE_SMC_CSR);
|
|
|
|
|
if (status & APPLE_SMC_STATUS_WRITE_ACCEPTED)
|
|
|
|
|
return 0;
|
|
|
|
|
DELAY(100);
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Write again if it hasn't been acknowledged at all. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if (!(status & APPLE_SMC_STATUS_WRITE_PENDING))
|
|
|
|
|
apple_smc_bus_write_1(smc, reg, byte);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ETIMEDOUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
apple_smc_write_cmd(struct apple_smc_tag *smc, uint8_t cmd)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return apple_smc_write(smc, APPLE_SMC_CSR, cmd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
apple_smc_write_data(struct apple_smc_tag *smc, uint8_t data)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return apple_smc_write(smc, APPLE_SMC_DATA, data);
|
|
|
|
|
}
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
2014-04-01 21:47:36 +04:00
|
|
|
|
static int
|
|
|
|
|
apple_smc_begin(struct apple_smc_tag *smc, uint8_t cmd, const char *key,
|
|
|
|
|
uint8_t size)
|
|
|
|
|
{
|
|
|
|
|
unsigned int i;
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
KASSERT(mutex_owned(&smc->smc_io_lock));
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Write the command first. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
error = apple_smc_write_cmd(smc, cmd);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Write the key next. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
|
error = apple_smc_write_data(smc, key[i]);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Finally, report how many bytes of data we want to send/receive. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
error = apple_smc_write_data(smc, size);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
apple_smc_input(struct apple_smc_tag *smc, uint8_t cmd, const char *key,
|
|
|
|
|
void *buffer, uint8_t size)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *bytes = buffer;
|
|
|
|
|
uint8_t i;
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Grab the SMC I/O lock. */
|
|
|
|
|
mutex_enter(&smc->smc_io_lock);
|
|
|
|
|
|
|
|
|
|
/* Initiate the command with this key. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
error = apple_smc_begin(smc, cmd, key, size);
|
|
|
|
|
if (error)
|
|
|
|
|
goto out;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Read each byte of data in sequence. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
|
error = apple_smc_read_data(smc, &bytes[i]);
|
|
|
|
|
if (error)
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Success! */
|
|
|
|
|
error = 0;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
out: mutex_exit(&smc->smc_io_lock);
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
apple_smc_output(struct apple_smc_tag *smc, uint8_t cmd, const char *key,
|
|
|
|
|
const void *buffer, uint8_t size)
|
|
|
|
|
{
|
|
|
|
|
const uint8_t *bytes = buffer;
|
|
|
|
|
uint8_t i;
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Grab the SMC I/O lock. */
|
|
|
|
|
mutex_enter(&smc->smc_io_lock);
|
|
|
|
|
|
|
|
|
|
/* Initiate the command with this key. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
error = apple_smc_begin(smc, cmd, key, size);
|
|
|
|
|
if (error)
|
|
|
|
|
goto out;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Write each byte of data in sequence. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
|
error = apple_smc_write_data(smc, bytes[i]);
|
|
|
|
|
if (error)
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Success! */
|
|
|
|
|
error = 0;
|
|
|
|
|
|
|
|
|
|
out: mutex_exit(&smc->smc_io_lock);
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return error;
|
|
|
|
|
}
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
|
|
|
|
struct apple_smc_key {
|
|
|
|
|
char ask_name[4 + 1];
|
|
|
|
|
struct apple_smc_desc ask_desc;
|
|
|
|
|
#ifdef DIAGNOSTIC
|
|
|
|
|
struct apple_smc_tag *ask_smc;
|
|
|
|
|
#endif
|
|
|
|
|
};
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
|
|
|
|
const char *
|
|
|
|
|
apple_smc_key_name(const struct apple_smc_key *key)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return key->ask_name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const struct apple_smc_desc *
|
|
|
|
|
apple_smc_key_desc(const struct apple_smc_key *key)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return &key->ask_desc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t
|
|
|
|
|
apple_smc_nkeys(struct apple_smc_tag *smc)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return smc->smc_nkeys;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_nth_key(struct apple_smc_tag *smc, uint32_t index,
|
|
|
|
|
const char type[4 + 1], struct apple_smc_key **keyp)
|
|
|
|
|
{
|
|
|
|
|
union { uint32_t u32; char name[4]; } index_be;
|
|
|
|
|
struct apple_smc_key *key;
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Paranoia: type must be NULL or 4 non-null characters long. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if ((type != NULL) && (strlen(type) != 4))
|
|
|
|
|
return EINVAL;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Create a new key. XXX Consider caching these. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
key = kmem_alloc(sizeof(*key), KM_SLEEP);
|
|
|
|
|
#ifdef DIAGNOSTIC
|
|
|
|
|
key->ask_smc = smc;
|
|
|
|
|
#endif
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Ask the SMC what the name of the key by this number is. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
index_be.u32 = htobe32(index);
|
|
|
|
|
error = apple_smc_input(smc, APPLE_SMC_CMD_NTH_KEY, index_be.name,
|
|
|
|
|
key->ask_name, 4);
|
|
|
|
|
if (error)
|
|
|
|
|
goto fail;
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Null-terminate the name. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
key->ask_name[4] = '\0';
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Ask the SMC for a description of this key by name. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
CTASSERT(sizeof(key->ask_desc) == 6);
|
|
|
|
|
error = apple_smc_input(smc, APPLE_SMC_CMD_KEY_DESC, key->ask_name,
|
|
|
|
|
&key->ask_desc, 6);
|
|
|
|
|
if (error)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Fail with EINVAL if the types don't match. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if ((type != NULL) && (0 != memcmp(key->ask_desc.asd_type, type, 4))) {
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Success! */
|
|
|
|
|
*keyp = key;
|
|
|
|
|
return 0;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
fail: kmem_free(key, sizeof(*key));
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_named_key(struct apple_smc_tag *smc, const char name[4 + 1],
|
|
|
|
|
const char type[4 + 1], struct apple_smc_key **keyp)
|
|
|
|
|
{
|
|
|
|
|
struct apple_smc_key *key;
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Paranoia: name must be 4 non-null characters long. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
KASSERT(name != NULL);
|
|
|
|
|
if (strlen(name) != 4)
|
|
|
|
|
return EINVAL;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Paranoia: type must be NULL or 4 non-null characters long. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if ((type != NULL) && (strlen(type) != 4))
|
|
|
|
|
return EINVAL;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Create a new key. XXX Consider caching these. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
key = kmem_alloc(sizeof(*key), KM_SLEEP);
|
|
|
|
|
#ifdef DIAGNOSTIC
|
|
|
|
|
key->ask_smc = smc;
|
|
|
|
|
#endif
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Use the specified name, and make sure it's null-terminated. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
(void)memcpy(key->ask_name, name, 4);
|
|
|
|
|
key->ask_name[4] = '\0';
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Ask the SMC for a description of this key by name. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
CTASSERT(sizeof(key->ask_desc) == 6);
|
|
|
|
|
error = apple_smc_input(smc, APPLE_SMC_CMD_KEY_DESC, key->ask_name,
|
|
|
|
|
&key->ask_desc, 6);
|
|
|
|
|
if (error)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Fail with EINVAL if the types don't match. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if ((type != NULL) && (0 != memcmp(key->ask_desc.asd_type, type, 4))) {
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Success! */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
*keyp = key;
|
|
|
|
|
return 0;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
fail: kmem_free(key, sizeof(*key));
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apple_smc_release_key(struct apple_smc_tag *smc, struct apple_smc_key *key)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
#ifdef DIAGNOSTIC
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Make sure the caller didn't mix up SMC tags. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if (key->ask_smc != smc)
|
|
|
|
|
aprint_error_dev(smc->smc_dev,
|
|
|
|
|
"releasing key with wrong tag: %p != %p",
|
|
|
|
|
smc, key->ask_smc);
|
|
|
|
|
#endif
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Nothing to do but free the key's memory. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
kmem_free(key, sizeof(*key));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_key_search(struct apple_smc_tag *smc, const char *name,
|
|
|
|
|
uint32_t *result)
|
|
|
|
|
{
|
|
|
|
|
struct apple_smc_key *key;
|
|
|
|
|
uint32_t start = 0, end = apple_smc_nkeys(smc), median;
|
2014-04-26 03:54:58 +04:00
|
|
|
|
int cmp;
|
2014-04-01 21:47:36 +04:00
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Do a binary search on the SMC's key space. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
while (start < end) {
|
|
|
|
|
median = (start + ((end - start) / 2));
|
|
|
|
|
error = apple_smc_nth_key(smc, median, NULL, &key);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
|
2014-04-26 03:54:58 +04:00
|
|
|
|
cmp = memcmp(name, apple_smc_key_name(key), 4);
|
|
|
|
|
if (cmp < 0)
|
2014-04-01 21:47:36 +04:00
|
|
|
|
end = median;
|
2014-04-26 03:54:58 +04:00
|
|
|
|
else if (cmp > 0)
|
2014-04-01 21:47:36 +04:00
|
|
|
|
start = (median + 1);
|
2014-04-26 03:54:58 +04:00
|
|
|
|
else
|
|
|
|
|
start = end = median; /* stop here */
|
|
|
|
|
|
2014-04-01 21:47:36 +04:00
|
|
|
|
apple_smc_release_key(smc, key);
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Success! */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
*result = start;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
2014-04-01 21:47:36 +04:00
|
|
|
|
int
|
|
|
|
|
apple_smc_read_key(struct apple_smc_tag *smc, const struct apple_smc_key *key,
|
|
|
|
|
void *buffer, uint8_t size)
|
|
|
|
|
{
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Refuse if software and hardware disagree on the key's size. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if (key->ask_desc.asd_size != size)
|
|
|
|
|
return EINVAL;
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Refuse if the hardware doesn't want us to read it. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if (!(key->ask_desc.asd_flags & APPLE_SMC_FLAG_READ))
|
|
|
|
|
return EACCES;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Looks good. Try reading it from the hardware. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return apple_smc_input(smc, APPLE_SMC_CMD_READ_KEY, key->ask_name,
|
|
|
|
|
buffer, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_read_key_1(struct apple_smc_tag *smc,
|
|
|
|
|
const struct apple_smc_key *key, uint8_t *p)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return apple_smc_read_key(smc, key, p, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_read_key_2(struct apple_smc_tag *smc,
|
|
|
|
|
const struct apple_smc_key *key, uint16_t *p)
|
|
|
|
|
{
|
|
|
|
|
uint16_t be;
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Read a big-endian quantity from the hardware. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
error = apple_smc_read_key(smc, key, &be, 2);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Convert it to host order. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
*p = be16toh(be);
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Success! */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_read_key_4(struct apple_smc_tag *smc,
|
|
|
|
|
const struct apple_smc_key *key, uint32_t *p)
|
|
|
|
|
{
|
|
|
|
|
uint32_t be;
|
|
|
|
|
int error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Read a big-endian quantity from the hardware. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
error = apple_smc_read_key(smc, key, &be, 4);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Convert it to host order. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
*p = be32toh(be);
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Success! */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_write_key(struct apple_smc_tag *smc, const struct apple_smc_key *key,
|
|
|
|
|
const void *buffer, uint8_t size)
|
|
|
|
|
{
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Refuse if software and hardware disagree on the key's size. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if (key->ask_desc.asd_size != size)
|
|
|
|
|
return EINVAL;
|
2014-04-01 21:49:05 +04:00
|
|
|
|
|
|
|
|
|
/* Refuse if the hardware doesn't want us to write it. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
if (!(key->ask_desc.asd_flags & APPLE_SMC_FLAG_WRITE))
|
|
|
|
|
return EACCES;
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Looks good. Try writing it to the hardware. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return apple_smc_output(smc, APPLE_SMC_CMD_WRITE_KEY, key->ask_name,
|
|
|
|
|
buffer, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_write_key_1(struct apple_smc_tag *smc,
|
|
|
|
|
const struct apple_smc_key *key, uint8_t v)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
return apple_smc_write_key(smc, key, &v, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_write_key_2(struct apple_smc_tag *smc,
|
|
|
|
|
const struct apple_smc_key *key, uint16_t v)
|
|
|
|
|
{
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Convert the quantity from host to big-endian byte order. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
const uint16_t v_be = htobe16(v);
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Write the big-endian quantity to the hardware. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return apple_smc_write_key(smc, key, &v_be, 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
apple_smc_write_key_4(struct apple_smc_tag *smc,
|
|
|
|
|
const struct apple_smc_key *key, uint32_t v)
|
|
|
|
|
{
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Convert the quantity from host to big-endian byte order. */
|
2014-04-01 21:49:17 +04:00
|
|
|
|
const uint32_t v_be = htobe32(v);
|
2014-04-01 21:47:36 +04:00
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Write the big-endian quantity to the hardware. */
|
2014-04-01 21:47:36 +04:00
|
|
|
|
return apple_smc_write_key(smc, key, &v_be, 4);
|
|
|
|
|
}
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
|
|
|
|
MODULE(MODULE_CLASS_MISC, apple_smc, NULL)
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
apple_smc_modcmd(modcmd_t cmd, void *data __unused)
|
|
|
|
|
{
|
|
|
|
|
|
2014-04-01 21:49:05 +04:00
|
|
|
|
/* Nothing to do for now to set up or tear down the module. */
|
2014-04-01 21:48:39 +04:00
|
|
|
|
switch (cmd) {
|
|
|
|
|
case MODULE_CMD_INIT:
|
2014-04-01 21:48:52 +04:00
|
|
|
|
return 0;
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
|
|
|
|
case MODULE_CMD_FINI:
|
2014-04-01 21:48:52 +04:00
|
|
|
|
return 0;
|
2014-04-01 21:48:39 +04:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return ENOTTY;
|
|
|
|
|
}
|
|
|
|
|
}
|