747 lines
19 KiB
C
747 lines
19 KiB
C
/*-
|
|
* Copyright (c) 2015 M. Warner Losh <imp@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 unmodified, 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/kernel.h>
|
|
|
|
#include <sys/bus.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/libkern.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <dev/ow/ow.h>
|
|
#include <dev/ow/owll.h>
|
|
#include <dev/ow/own.h>
|
|
|
|
/*
|
|
* lldev - link level device
|
|
* ndev - network / transport device (this module)
|
|
* pdev - presentation device (children of this module)
|
|
*/
|
|
|
|
typedef int ow_enum_fn(device_t, device_t);
|
|
typedef int ow_found_fn(device_t, romid_t);
|
|
|
|
struct ow_softc
|
|
{
|
|
device_t dev; /* Newbus driver back pointer */
|
|
struct mtx mtx; /* bus mutex */
|
|
device_t owner; /* bus owner, if != NULL */
|
|
};
|
|
|
|
struct ow_devinfo
|
|
{
|
|
romid_t romid;
|
|
};
|
|
|
|
static int ow_acquire_bus(device_t ndev, device_t pdev, int how);
|
|
static void ow_release_bus(device_t ndev, device_t pdev);
|
|
|
|
#define OW_LOCK(_sc) mtx_lock(&(_sc)->mtx)
|
|
#define OW_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx)
|
|
#define OW_LOCK_DESTROY(_sc) mtx_destroy(&_sc->mtx)
|
|
#define OW_ASSERT_LOCKED(_sc) mtx_assert(&_sc->mtx, MA_OWNED)
|
|
#define OW_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED)
|
|
|
|
static MALLOC_DEFINE(M_OW, "ow", "House keeping data for 1wire bus");
|
|
|
|
static const struct ow_timing timing_regular_min = {
|
|
.t_slot = 60,
|
|
.t_low0 = 60,
|
|
.t_low1 = 1,
|
|
.t_release = 0,
|
|
.t_rec = 1,
|
|
.t_rdv = 15, /* fixed */
|
|
.t_rstl = 480,
|
|
.t_rsth = 480,
|
|
.t_pdl = 60,
|
|
.t_pdh = 15,
|
|
.t_lowr = 1,
|
|
};
|
|
|
|
static const struct ow_timing timing_regular_max = {
|
|
.t_slot = 120,
|
|
.t_low0 = 120,
|
|
.t_low1 = 15,
|
|
.t_release = 45,
|
|
.t_rec = 960, /* infinity */
|
|
.t_rdv = 15, /* fixed */
|
|
.t_rstl = 960, /* infinity */
|
|
.t_rsth = 960, /* infinity */
|
|
.t_pdl = 240, /* 60us to 240us */
|
|
.t_pdh = 60, /* 15us to 60us */
|
|
.t_lowr = 15, /* 1us */
|
|
};
|
|
|
|
static struct ow_timing timing_regular = {
|
|
.t_slot = 60, /* 60 <= t < 120 */
|
|
.t_low0 = 60, /* 60 <= t < t_slot < 120 */
|
|
.t_low1 = 1, /* 1 <= t < 15 */
|
|
.t_release = 45, /* 0 <= t < 45 */
|
|
.t_rec = 15, /* 1 <= t < inf */
|
|
.t_rdv = 15, /* t == 15 */
|
|
.t_rstl = 480, /* 480 <= t < inf */
|
|
.t_rsth = 480, /* 480 <= t < inf */
|
|
.t_pdl = 60, /* 60 <= t < 240 */
|
|
.t_pdh = 60, /* 15 <= t < 60 */
|
|
.t_lowr = 1, /* 1 <= t < 15 */
|
|
};
|
|
|
|
/* NB: Untested */
|
|
static const struct ow_timing timing_overdrive_min = {
|
|
.t_slot = 6,
|
|
.t_low0 = 6,
|
|
.t_low1 = 1,
|
|
.t_release = 0,
|
|
.t_rec = 1,
|
|
.t_rdv = 2, /* fixed */
|
|
.t_rstl = 48,
|
|
.t_rsth = 48,
|
|
.t_pdl = 8,
|
|
.t_pdh = 2,
|
|
.t_lowr = 1,
|
|
};
|
|
|
|
static const struct ow_timing timing_overdrive_max = {
|
|
.t_slot = 16,
|
|
.t_low0 = 16,
|
|
.t_low1 = 2,
|
|
.t_release = 4,
|
|
.t_rec = 960, /* infinity */
|
|
.t_rdv = 2, /* fixed */
|
|
.t_rstl = 80,
|
|
.t_rsth = 960, /* infinity */
|
|
.t_pdl = 24,
|
|
.t_pdh = 6,
|
|
.t_lowr = 2,
|
|
};
|
|
|
|
static struct ow_timing timing_overdrive = {
|
|
.t_slot = 11, /* 6 <= t < 16 */
|
|
.t_low0 = 6, /* 6 <= t < t_slot < 16 */
|
|
.t_low1 = 1, /* 1 <= t < 2 */
|
|
.t_release = 4, /* 0 <= t < 4 */
|
|
.t_rec = 1, /* 1 <= t < inf */
|
|
.t_rdv = 2, /* t == 2 */
|
|
.t_rstl = 48, /* 48 <= t < 80 */
|
|
.t_rsth = 48, /* 48 <= t < inf */
|
|
.t_pdl = 8, /* 8 <= t < 24 */
|
|
.t_pdh = 2, /* 2 <= t < 6 */
|
|
.t_lowr = 1, /* 1 <= t < 2 */
|
|
};
|
|
|
|
SYSCTL_NODE(_hw, OID_AUTO, ow, CTLFLAG_RD, 0, "1-Wire protocol");
|
|
SYSCTL_NODE(_hw_ow, OID_AUTO, regular, CTLFLAG_RD, 0,
|
|
"Regular mode timings");
|
|
SYSCTL_NODE(_hw_ow, OID_AUTO, overdrive, CTLFLAG_RD, 0,
|
|
"Overdrive mode timings");
|
|
|
|
#define _OW_TIMING_SYSCTL(mode, param) \
|
|
static int \
|
|
sysctl_ow_timing_ ## mode ## _ ## param(SYSCTL_HANDLER_ARGS) \
|
|
{ \
|
|
int val = timing_ ## mode.param; \
|
|
int err; \
|
|
err = sysctl_handle_int(oidp, &val, 0, req); \
|
|
if (err != 0 || req->newptr == NULL) \
|
|
return (err); \
|
|
if (val < timing_ ## mode ## _min.param) \
|
|
return (EINVAL); \
|
|
else if (val >= timing_ ## mode ## _max.param) \
|
|
return (EINVAL); \
|
|
timing_ ## mode.param = val; \
|
|
return (0); \
|
|
} \
|
|
SYSCTL_PROC(_hw_ow_ ## mode, OID_AUTO, param, \
|
|
CTLTYPE_INT | CTLFLAG_RWTUN, 0, sizeof(int), \
|
|
sysctl_ow_timing_ ## mode ## _ ## param, "I", \
|
|
"1-Wire timing parameter in microseconds (-1 resets to default)")
|
|
|
|
#define OW_TIMING_SYSCTL(param) \
|
|
_OW_TIMING_SYSCTL(regular, param); \
|
|
_OW_TIMING_SYSCTL(overdrive, param)
|
|
|
|
OW_TIMING_SYSCTL(t_slot);
|
|
OW_TIMING_SYSCTL(t_low0);
|
|
OW_TIMING_SYSCTL(t_low1);
|
|
OW_TIMING_SYSCTL(t_release);
|
|
OW_TIMING_SYSCTL(t_rec);
|
|
OW_TIMING_SYSCTL(t_rdv);
|
|
OW_TIMING_SYSCTL(t_rstl);
|
|
OW_TIMING_SYSCTL(t_rsth);
|
|
OW_TIMING_SYSCTL(t_pdl);
|
|
OW_TIMING_SYSCTL(t_pdh);
|
|
OW_TIMING_SYSCTL(t_lowr);
|
|
|
|
#undef _OW_TIMING_SYSCTL
|
|
#undef OW_TIMING_SYSCTL
|
|
|
|
static void
|
|
ow_send_byte(device_t lldev, struct ow_timing *t, uint8_t byte)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
if (byte & (1 << i))
|
|
OWLL_WRITE_ONE(lldev, t);
|
|
else
|
|
OWLL_WRITE_ZERO(lldev, t);
|
|
}
|
|
|
|
static void
|
|
ow_read_byte(device_t lldev, struct ow_timing *t, uint8_t *bytep)
|
|
{
|
|
int i;
|
|
uint8_t byte = 0;
|
|
int bit;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
OWLL_READ_DATA(lldev, t, &bit);
|
|
byte |= bit << i;
|
|
}
|
|
*bytep = byte;
|
|
}
|
|
|
|
static int
|
|
ow_send_command(device_t ndev, device_t pdev, struct ow_cmd *cmd)
|
|
{
|
|
int present, i, bit, tries;
|
|
device_t lldev;
|
|
struct ow_timing *t;
|
|
|
|
lldev = device_get_parent(ndev);
|
|
|
|
/*
|
|
* Retry the reset a couple of times before giving up.
|
|
*/
|
|
tries = 4;
|
|
do {
|
|
OWLL_RESET_AND_PRESENCE(lldev, &timing_regular, &present);
|
|
if (present == 1)
|
|
device_printf(ndev, "Reset said no device on bus?.\n");
|
|
} while (present == 1 && tries-- > 0);
|
|
if (present == 1) {
|
|
device_printf(ndev, "Reset said the device wasn't there.\n");
|
|
return ENOENT; /* No devices acked the RESET */
|
|
}
|
|
if (present == -1) {
|
|
device_printf(ndev, "Reset discovered bus wired wrong.\n");
|
|
return ENOENT;
|
|
}
|
|
|
|
for (i = 0; i < cmd->rom_len; i++)
|
|
ow_send_byte(lldev, &timing_regular, cmd->rom_cmd[i]);
|
|
for (i = 0; i < cmd->rom_read_len; i++)
|
|
ow_read_byte(lldev, &timing_regular, cmd->rom_read + i);
|
|
if (cmd->xpt_len) {
|
|
/*
|
|
* Per AN937, the reset pulse and ROM level are always
|
|
* done with the regular timings. Certain ROM commands
|
|
* put the device into overdrive mode for the remainder
|
|
* of the data transfer, which is why we have to pass the
|
|
* timings here. Commands that need to be handled like this
|
|
* are expected to be flagged by the client.
|
|
*/
|
|
t = (cmd->flags & OW_FLAG_OVERDRIVE) ?
|
|
&timing_overdrive : &timing_regular;
|
|
for (i = 0; i < cmd->xpt_len; i++)
|
|
ow_send_byte(lldev, t, cmd->xpt_cmd[i]);
|
|
if (cmd->flags & OW_FLAG_READ_BIT) {
|
|
memset(cmd->xpt_read, 0, (cmd->xpt_read_len + 7) / 8);
|
|
for (i = 0; i < cmd->xpt_read_len; i++) {
|
|
OWLL_READ_DATA(lldev, t, &bit);
|
|
cmd->xpt_read[i / 8] |= bit << (i % 8);
|
|
}
|
|
} else {
|
|
for (i = 0; i < cmd->xpt_read_len; i++)
|
|
ow_read_byte(lldev, t, cmd->xpt_read + i);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ow_search_rom(device_t lldev, device_t dev)
|
|
{
|
|
struct ow_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.rom_cmd[0] = SEARCH_ROM;
|
|
cmd.rom_len = 1;
|
|
return ow_send_command(lldev, dev, &cmd);
|
|
}
|
|
|
|
#if 0
|
|
static int
|
|
ow_alarm_search(device_t lldev, device_t dev)
|
|
{
|
|
struct ow_cmd cmd;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.rom_cmd[0] = ALARM_SEARCH;
|
|
cmd.rom_len = 1;
|
|
return ow_send_command(lldev, dev, &cmd);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
ow_add_child(device_t dev, romid_t romid)
|
|
{
|
|
struct ow_devinfo *di;
|
|
device_t child;
|
|
|
|
di = malloc(sizeof(*di), M_OW, M_WAITOK);
|
|
di->romid = romid;
|
|
child = device_add_child(dev, NULL, -1);
|
|
if (child == NULL) {
|
|
free(di, M_OW);
|
|
return ENOMEM;
|
|
}
|
|
device_set_ivars(child, di);
|
|
return (0);
|
|
}
|
|
|
|
static device_t
|
|
ow_child_by_romid(device_t dev, romid_t romid)
|
|
{
|
|
device_t *children, retval, child;
|
|
int nkid, i;
|
|
struct ow_devinfo *di;
|
|
|
|
if (device_get_children(dev, &children, &nkid) != 0)
|
|
return (NULL);
|
|
retval = NULL;
|
|
for (i = 0; i < nkid; i++) {
|
|
child = children[i];
|
|
di = device_get_ivars(child);
|
|
if (di->romid == romid) {
|
|
retval = child;
|
|
break;
|
|
}
|
|
}
|
|
free(children, M_TEMP);
|
|
|
|
return (retval);
|
|
}
|
|
|
|
/*
|
|
* CRC generator table -- taken from AN937 DOW CRC LOOKUP FUNCTION Table 2
|
|
*/
|
|
const uint8_t ow_crc_table[] = {
|
|
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
|
|
157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
|
|
35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
|
|
190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
|
|
70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
|
|
219, 133,103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
|
|
101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
|
|
248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
|
|
140,210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113,147, 205,
|
|
17, 79, 173, 243, 112, 46, 204, 146, 211,141, 111, 49, 178, 236, 14, 80,
|
|
175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82,176, 238,
|
|
50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
|
|
202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
|
|
87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
|
|
233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
|
|
116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
|
|
};
|
|
|
|
/*
|
|
* Converted from DO_CRC page 131 ANN937
|
|
*/
|
|
static uint8_t
|
|
ow_crc(device_t ndev, device_t pdev, uint8_t *buffer, size_t len)
|
|
{
|
|
uint8_t crc = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
crc = ow_crc_table[crc ^ buffer[i]];
|
|
return crc;
|
|
}
|
|
|
|
static int
|
|
ow_check_crc(romid_t romid)
|
|
{
|
|
return ow_crc(NULL, NULL, (uint8_t *)&romid, sizeof(romid)) == 0;
|
|
}
|
|
|
|
static int
|
|
ow_device_found(device_t dev, romid_t romid)
|
|
{
|
|
|
|
/* XXX Move this up into enumerate? */
|
|
/*
|
|
* All valid ROM IDs have a valid CRC. Check that first.
|
|
*/
|
|
if (!ow_check_crc(romid)) {
|
|
device_printf(dev, "Device romid %8D failed CRC.\n",
|
|
&romid, ":");
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* If we've seen this child before, don't add a new one for it.
|
|
*/
|
|
if (ow_child_by_romid(dev, romid) != NULL)
|
|
return 0;
|
|
|
|
return ow_add_child(dev, romid);
|
|
}
|
|
|
|
static int
|
|
ow_enumerate(device_t dev, ow_enum_fn *enumfp, ow_found_fn *foundfp)
|
|
{
|
|
device_t lldev = device_get_parent(dev);
|
|
int first, second, i, dir, prior, last, err, retries;
|
|
uint64_t probed, last_mask;
|
|
int sanity = 10;
|
|
|
|
prior = -1;
|
|
last_mask = 0;
|
|
retries = 0;
|
|
last = -2;
|
|
err = ow_acquire_bus(dev, dev, OWN_DONTWAIT);
|
|
if (err != 0)
|
|
return err;
|
|
while (last != -1) {
|
|
if (sanity-- < 0) {
|
|
printf("Reached the sanity limit\n");
|
|
return EIO;
|
|
}
|
|
again:
|
|
probed = 0;
|
|
last = -1;
|
|
|
|
/*
|
|
* See AN397 section 5.II.C.3 for the algorithm (though a bit
|
|
* poorly stated). The search command forces each device to
|
|
* send ROM ID bits one at a time (first the bit, then the
|
|
* complement) the master (us) sends back a bit. If the
|
|
* device's bit doesn't match what we send back, that device
|
|
* stops sending bits back. So each time through we remember
|
|
* where we made the last decision (always 0). If there's a
|
|
* conflict there this time (and there will be in the absence
|
|
* of a hardware failure) we go with 1. This way, we prune the
|
|
* devices on the bus and wind up with a unique ROM. We know
|
|
* we're done when we detect no new conflicts. The same
|
|
* algorithm is used for devices in alarm state as well.
|
|
*
|
|
* In addition, experience has shown that sometimes devices
|
|
* stop responding in the middle of enumeration, so try this
|
|
* step again a few times when that happens. It is unclear if
|
|
* this is due to a nosiy electrical environment or some odd
|
|
* timing issue.
|
|
*/
|
|
|
|
/*
|
|
* The enumeration command should be successfully sent, if not,
|
|
* we have big issues on the bus so punt. Lower layers report
|
|
* any unusual errors, so we don't need to here.
|
|
*/
|
|
err = enumfp(dev, dev);
|
|
if (err != 0)
|
|
return (err);
|
|
|
|
for (i = 0; i < 64; i++) {
|
|
OWLL_READ_DATA(lldev, &timing_regular, &first);
|
|
OWLL_READ_DATA(lldev, &timing_regular, &second);
|
|
switch (first | second << 1) {
|
|
case 0: /* Conflict */
|
|
if (i < prior)
|
|
dir = (last_mask >> i) & 1;
|
|
else
|
|
dir = i == prior;
|
|
|
|
if (dir == 0)
|
|
last = i;
|
|
break;
|
|
case 1: /* 1 then 0 -> 1 for all */
|
|
dir = 1;
|
|
break;
|
|
case 2: /* 0 then 1 -> 0 for all */
|
|
dir = 0;
|
|
break;
|
|
case 3:
|
|
/*
|
|
* No device responded. This is unexpected, but
|
|
* experience has shown that on some platforms
|
|
* we miss a timing window, or otherwise have
|
|
* an issue. Start this step over. Since we've
|
|
* not updated prior yet, we can just jump to
|
|
* the top of the loop for a re-do of this step.
|
|
*/
|
|
printf("oops, starting over\n");
|
|
if (++retries > 5)
|
|
return (EIO);
|
|
goto again;
|
|
default: /* NOTREACHED */
|
|
__unreachable();
|
|
}
|
|
if (dir) {
|
|
OWLL_WRITE_ONE(lldev, &timing_regular);
|
|
probed |= 1ull << i;
|
|
} else {
|
|
OWLL_WRITE_ZERO(lldev, &timing_regular);
|
|
}
|
|
}
|
|
retries = 0;
|
|
foundfp(dev, probed);
|
|
last_mask = probed;
|
|
prior = last;
|
|
}
|
|
ow_release_bus(dev, dev);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ow_probe(device_t dev)
|
|
{
|
|
|
|
device_set_desc(dev, "1 Wire Bus");
|
|
return (BUS_PROBE_GENERIC);
|
|
}
|
|
|
|
static int
|
|
ow_attach(device_t ndev)
|
|
{
|
|
struct ow_softc *sc;
|
|
|
|
/*
|
|
* Find all the devices on the bus. We don't probe / attach them in the
|
|
* enumeration phase. We do this because we want to allow the probe /
|
|
* attach routines of the child drivers to have as full an access to the
|
|
* bus as possible. While we reset things before the next step of the
|
|
* search (so it would likely be OK to allow access by the clients to
|
|
* the bus), it is more conservative to find them all, then to do the
|
|
* attach of the devices. This also allows the child devices to have
|
|
* more knowledge of the bus. We also ignore errors from the enumeration
|
|
* because they might happen after we've found a few devices.
|
|
*/
|
|
sc = device_get_softc(ndev);
|
|
sc->dev = ndev;
|
|
mtx_init(&sc->mtx, device_get_nameunit(sc->dev), "ow", MTX_DEF);
|
|
ow_enumerate(ndev, ow_search_rom, ow_device_found);
|
|
return bus_generic_attach(ndev);
|
|
}
|
|
|
|
static int
|
|
ow_detach(device_t ndev)
|
|
{
|
|
device_t *children, child;
|
|
int nkid, i;
|
|
struct ow_devinfo *di;
|
|
struct ow_softc *sc;
|
|
|
|
sc = device_get_softc(ndev);
|
|
/*
|
|
* detach all the children first. This is blocking until any threads
|
|
* have stopped, etc.
|
|
*/
|
|
bus_generic_detach(ndev);
|
|
|
|
/*
|
|
* We delete all the children, and free up the ivars
|
|
*/
|
|
if (device_get_children(ndev, &children, &nkid) != 0)
|
|
return ENOMEM;
|
|
for (i = 0; i < nkid; i++) {
|
|
child = children[i];
|
|
di = device_get_ivars(child);
|
|
free(di, M_OW);
|
|
device_delete_child(ndev, child);
|
|
}
|
|
free(children, M_TEMP);
|
|
|
|
OW_LOCK_DESTROY(sc);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Not sure this is really needed. I'm having trouble figuring out what
|
|
* location means in the context of the one wire bus.
|
|
*/
|
|
static int
|
|
ow_child_location_str(device_t dev, device_t child, char *buf,
|
|
size_t buflen)
|
|
{
|
|
|
|
*buf = '\0';
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ow_child_pnpinfo_str(device_t dev, device_t child, char *buf,
|
|
size_t buflen)
|
|
{
|
|
struct ow_devinfo *di;
|
|
|
|
di = device_get_ivars(child);
|
|
snprintf(buf, buflen, "romid=%8D", &di->romid, ":");
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ow_read_ivar(device_t dev, device_t child, int which, uintptr_t *result)
|
|
{
|
|
struct ow_devinfo *di;
|
|
romid_t **ptr;
|
|
|
|
di = device_get_ivars(child);
|
|
switch (which) {
|
|
case OW_IVAR_FAMILY:
|
|
*result = di->romid & 0xff;
|
|
break;
|
|
case OW_IVAR_ROMID:
|
|
ptr = (romid_t **)result;
|
|
*ptr = &di->romid;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ow_write_ivar(device_t dev, device_t child, int which, uintptr_t value)
|
|
{
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
static int
|
|
ow_print_child(device_t ndev, device_t pdev)
|
|
{
|
|
int retval = 0;
|
|
struct ow_devinfo *di;
|
|
|
|
di = device_get_ivars(pdev);
|
|
|
|
retval += bus_print_child_header(ndev, pdev);
|
|
retval += printf(" romid %8D", &di->romid, ":");
|
|
retval += bus_print_child_footer(ndev, pdev);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
ow_probe_nomatch(device_t ndev, device_t pdev)
|
|
{
|
|
struct ow_devinfo *di;
|
|
|
|
di = device_get_ivars(pdev);
|
|
device_printf(ndev, "romid %8D: no driver\n", &di->romid, ":");
|
|
}
|
|
|
|
static int
|
|
ow_acquire_bus(device_t ndev, device_t pdev, int how)
|
|
{
|
|
struct ow_softc *sc;
|
|
|
|
sc = device_get_softc(ndev);
|
|
OW_ASSERT_UNLOCKED(sc);
|
|
OW_LOCK(sc);
|
|
if (sc->owner != NULL) {
|
|
if (sc->owner == pdev)
|
|
panic("%s: %s recursively acquiring the bus.\n",
|
|
device_get_nameunit(ndev),
|
|
device_get_nameunit(pdev));
|
|
if (how == OWN_DONTWAIT) {
|
|
OW_UNLOCK(sc);
|
|
return EWOULDBLOCK;
|
|
}
|
|
while (sc->owner != NULL)
|
|
mtx_sleep(sc, &sc->mtx, 0, "owbuswait", 0);
|
|
}
|
|
sc->owner = pdev;
|
|
OW_UNLOCK(sc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
ow_release_bus(device_t ndev, device_t pdev)
|
|
{
|
|
struct ow_softc *sc;
|
|
|
|
sc = device_get_softc(ndev);
|
|
OW_ASSERT_UNLOCKED(sc);
|
|
OW_LOCK(sc);
|
|
if (sc->owner == NULL)
|
|
panic("%s: %s releasing unowned bus.", device_get_nameunit(ndev),
|
|
device_get_nameunit(pdev));
|
|
if (sc->owner != pdev)
|
|
panic("%s: %s don't own the bus. %s does. game over.",
|
|
device_get_nameunit(ndev), device_get_nameunit(pdev),
|
|
device_get_nameunit(sc->owner));
|
|
sc->owner = NULL;
|
|
wakeup(sc);
|
|
OW_UNLOCK(sc);
|
|
}
|
|
|
|
devclass_t ow_devclass;
|
|
|
|
static device_method_t ow_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, ow_probe),
|
|
DEVMETHOD(device_attach, ow_attach),
|
|
DEVMETHOD(device_detach, ow_detach),
|
|
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_child_pnpinfo_str, ow_child_pnpinfo_str),
|
|
DEVMETHOD(bus_child_location_str, ow_child_location_str),
|
|
DEVMETHOD(bus_read_ivar, ow_read_ivar),
|
|
DEVMETHOD(bus_write_ivar, ow_write_ivar),
|
|
DEVMETHOD(bus_print_child, ow_print_child),
|
|
DEVMETHOD(bus_probe_nomatch, ow_probe_nomatch),
|
|
|
|
/* One Wire Network/Transport layer interface */
|
|
DEVMETHOD(own_send_command, ow_send_command),
|
|
DEVMETHOD(own_acquire_bus, ow_acquire_bus),
|
|
DEVMETHOD(own_release_bus, ow_release_bus),
|
|
DEVMETHOD(own_crc, ow_crc),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t ow_driver = {
|
|
"ow",
|
|
ow_methods,
|
|
sizeof(struct ow_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(ow, owc, ow_driver, ow_devclass, 0, 0);
|
|
MODULE_VERSION(ow, 1);
|