/*-
* 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.
*/
/*
* If this device has already attached, don't attach it again.
*
* XXX This is a pretty silly way to query the children, but
* struct device doesn't seem to list its children.
*/
for (dev = deviter_first(&di, DEVITER_F_LEAVES_FIRST);
dev != NULL;
dev = deviter_next(&di)) {
if (device_parent(dev) != parent)
continue;
if (!device_is_a(dev, cf->cf_name))
continue;
attached = true;
break;
}
deviter_release(&di);
if (attached)
return 0;
/* If this device doesn't match, don't attach it. */
if (!config_probe(parent, cf, aux))
return 0;
/*
* 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;
KASSERT(mutex_owned(&smc->smc_io_lock));
/*
* Wait until the status register says there's data to read and
* read it.
*/
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;
KASSERT(mutex_owned(&smc->smc_io_lock));
/*
* Write the byte and then wait until the status register says
* it has been accepted.
*/
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);
/* Write again if it hasn't been acknowledged at all. */
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)
{
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;
/* Paranoia: type must be NULL or 4 non-null characters long. */
if ((type != NULL) && (strlen(type) != 4))
return EINVAL;
/* Create a new key. XXX Consider caching these. */
key = kmem_alloc(sizeof(*key), KM_SLEEP);
#ifdef DIAGNOSTIC
key->ask_smc = smc;
#endif
/* Ask the SMC what the name of the key by this number is. */
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;
/* Null-terminate the name. */
key->ask_name[4] = '\0';
/* Ask the SMC for a description of this key by name. */
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;
/* Fail with EINVAL if the types don't match. */
if ((type != NULL) && (0 != memcmp(key->ask_desc.asd_type, type, 4))) {
error = EINVAL;
goto fail;
}
/* Paranoia: name must be 4 non-null characters long. */
KASSERT(name != NULL);
if (strlen(name) != 4)
return EINVAL;
/* Paranoia: type must be NULL or 4 non-null characters long. */
if ((type != NULL) && (strlen(type) != 4))
return EINVAL;
/* Create a new key. XXX Consider caching these. */
key = kmem_alloc(sizeof(*key), KM_SLEEP);
#ifdef DIAGNOSTIC
key->ask_smc = smc;
#endif
/* Use the specified name, and make sure it's null-terminated. */
(void)memcpy(key->ask_name, name, 4);
key->ask_name[4] = '\0';
/* Ask the SMC for a description of this key by name. */
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;
/* Fail with EINVAL if the types don't match. */
if ((type != NULL) && (0 != memcmp(key->ask_desc.asd_type, type, 4))) {
error = EINVAL;
goto fail;
}
#ifdef DIAGNOSTIC
/* Make sure the caller didn't mix up SMC tags. */
if (key->ask_smc != smc)
aprint_error_dev(smc->smc_dev,
"releasing key with wrong tag: %p != %p",
smc, key->ask_smc);
#endif
/* Nothing to do but free the key's memory. */
kmem_free(key, sizeof(*key));
}
int
apple_smc_key_search(struct apple_smc_tag *smc, const char name[4 + 1],
uint32_t *result)
{
struct apple_smc_key *key;
uint32_t start = 0, end = apple_smc_nkeys(smc), median;
int cmp;
int error;
/* Do a binary search on the SMC's key space. */
while (start < end) {
median = (start + ((end - start) / 2));
error = apple_smc_nth_key(smc, median, NULL, &key);
if (error)
return error;
cmp = memcmp(name, apple_smc_key_name(key), 4);
if (cmp < 0)
end = median;
else if (cmp > 0)
start = (median + 1);
else
start = end = median; /* stop here */