rmc_comm.c revision 6fa6856ee31afb8f296ddb9f5201cfc9cc5ad729
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* The "rmc_comm" driver provides access to the RMC so that its clients need
* not be concerned with the details of the access mechanism, which in this
* case is implemented via a packet-based protocol over a serial link via a
* 16550 compatible serial port.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Header files
*/
#include <sys/rmc_comm_dp_boot.h>
#include <sys/rmc_comm_dp.h>
#include <sys/rmc_comm_drvintf.h>
#include <sys/rmc_comm.h>
#include <sys/cpu_sgnblk_defs.h>
/*
* Local definitions
*/
#define MYNAME "rmc_comm"
#define DUMMY_VALUE (~(int8_t)0)
/*
* Local data
*/
static void *rmc_comm_statep;
static kmutex_t rmc_comm_attach_lock;
{
};
static int watchdog_was_active;
extern int watchdog_activated;
extern int watchdog_enable;
/*
* prototypes
*/
static void sio_check_fault_status(struct rmc_comm_state *);
static void rmc_comm_cyclic(void *);
static void rmc_comm_hw_reset(struct rmc_comm_state *);
static void rmc_comm_offline(struct rmc_comm_state *);
/*
* for client leaf drivers to register their desire for rmc_comm
* to stay attached
*/
int
{
struct rmc_comm_state *rcs;
return (DDI_FAILURE);
}
rcs->n_registrations++;
return (DDI_SUCCESS);
}
void
{
struct rmc_comm_state *rcs;
rcs->n_registrations--;
}
/*
* to get the soft state structure of a specific instance
*/
struct rmc_comm_state *
{
/*
* Use the instance number from the <dip>; also,
* check that it really corresponds to this driver
*/
else if (dmaj != rmc_comm_major) {
"%s: major number mismatch (%d vs. %d) in %s(),"
"probably due to child misconfiguration",
instance = -1;
}
}
if (instance >= 0)
"%s: devinfo mismatch (%p vs. %p) in %s(), "
"probably due to child misconfiguration", MYNAME,
}
}
return (rcs);
}
/*
*/
static void
{
/*
* The chip is mapped as "I/O" (e.g. with the side-effect
* bit on SPARC), therefore accesses are required to be
* in-order, with no value cacheing. However, there can
* still be write-behind buffering, so it is not guaranteed
* that a write actually reaches the chip in a given time.
*
* To force the access right through to the chip, we follow
* the write with another write (to the SCRATCH register)
* and a read (of the value just written to the SCRATCH
* register). The SCRATCH register is specifically provided
* for temporary data and has no effect on the SIO's own
* operation, making it ideal as a synchronising mechanism.
*
* If we didn't do this, it would be possible that the new
* value wouldn't reach the chip (and have the *intended*
* side-effects, such as disabling interrupts), for such a
* long time that the processor could execute a *lot* of
* instructions - including exiting the interrupt service
* routine and re-enabling interrupts. This effect was
* observed to lead to spurious (unclaimed) interrupts in
* some circumstances.
*
* This will no longer be needed once "synchronous" access
* handles are available (see PSARC/2000/269 and 2000/531).
*/
membar_sync();
}
}
static uint8_t
{
else
val = DUMMY_VALUE;
return (val);
}
static void
{
}
{
}
/*
* Check for data ready.
*/
static boolean_t
{
/*
* Data is available if the RXDA bit in the LSR is nonzero
* (if reading it didn't incur a fault).
*/
}
/*
*/
static void
{
}
/*
* High-level interrupt handler:
* Checks whether initialisation is complete (to avoid a race
* with mutex_init()), and whether chip interrupts are enabled.
* If not, the interrupt's not for us, so just return UNCLAIMED.
* Otherwise, disable the interrupt, trigger a softint, and return
* CLAIMED. The softint handler will then do all the real work.
*
* NOTE: the chip interrupt capability is only re-enabled once the
* receive code has run, but that can be called from a poll loop
* or cyclic callback as well as from the softint. So it's *not*
* guaranteed that there really is a chip interrupt pending here,
* 'cos the work may already have been done and the reason for the
* interrupt gone away before we get here.
*
* OTOH, if we come through here twice without the receive code
* having run in between, that's definitely wrong. In such an
* event, we would notice that chip interrupts haven't yet been
* re-enabled and return UNCLAIMED, allowing the system's jabber
* protect code (if any) to do its job.
*/
static uint_t
{
/*
* Handle the case where this interrupt fires during
* panic processing. If that occurs, then a thread
* in rmc_comm might have been idled while holding
* hw_mutex. If so, that thread will never make
* progress, and so we do not want to unconditionally
* grab hw_mutex.
*/
if (ddi_in_panic() != 0) {
return (claim);
}
} else {
}
}
}
return (claim);
}
/*
* Packet receive handler
*
* This routine should be called from the low-level softint, or the
* cyclic callback, or rmc_comm_cmd() (for polled operation), with the
* low-level mutex already held.
*/
void
{
/*
* Check for access faults before starting the receive
* loop (we don't want to cause bus errors or suchlike
* unpleasantness in the event that the SIO has died).
*/
if (!rmc_comm_faulty(rcs)) {
/*
* Read bytes from the FIFO until they're all gone
* or our buffer overflows (which must be an error)
*/
/*
* At the moment, the receive buffer is overwritten any
* time data is received from the serial device.
* This should not pose problems (probably!) as the data
* protocol is half-duplex
* Otherwise, a circular buffer must be implemented!
*/
while (sio_data_ready(rcs)) {
if (rx_buflen >= SIO_MAX_RXBUF_SIZE)
break;
}
/*
* call up the data protocol receive handler
*/
}
}
/*
* Low-level softint handler
*
* This routine should be triggered whenever there's a byte to be read
*/
static uint_t
{
return (DDI_INTR_CLAIMED);
}
/*
* Cyclic handler: just calls the receive routine, in case interrupts
* are not being delivered and in order to handle command timeout
*/
static void
rmc_comm_cyclic(void *arg)
{
}
/*
* Serial protocol
*
* This routine builds a command and sets it in progress.
*/
void
{
uint8_t *p;
/*
* Check and update the SIO h/w fault status before accessing
* the chip registers. If there's a (new or previous) fault,
* we'll run through the protocol but won't really touch the
* hardware and all commands will timeout. If a previously
* discovered fault has now gone away (!), then we can (try to)
* proceed with the new command (probably a probe).
*/
/*
* Send the command now by stuffing the packet into the Tx FIFO.
*/
/*
* before writing to the TX holding register, we make sure that
* it is empty. In this case, there will be no chance to
* overflow the serial device FIFO (but, on the other hand,
* it may introduce some latency)
*/
while ((status & SIO_LSR_XHRE) == 0) {
drv_usecwait(100);
}
}
}
/*
* wait for the tx fifo to drain - used for urgent nowait requests
*/
void
{
while ((status & SIO_LSR_XHRE) == 0) {
drv_usecwait(100);
}
}
/*
* Hardware setup - put the SIO chip in the required operational
* state, with all our favourite parameters programmed correctly.
* This routine leaves all SIO interrupts disabled.
*/
static void
{
/*
* Disable interrupts, soft reset Tx and Rx circuitry,
*/
/*
* Select the proper baud rate; if the value is invalid
* (presumably 0, i.e. not specified, but also if the
* "baud" property is set to some silly value), we assume
* the default.
*/
} else {
}
/*
* According to the datasheet, it is forbidden for the divisor
* register to be zero. So when loading the register in two
* steps, we have to make sure that the temporary value formed
* between loads is nonzero. However, we can't rely on either
* half already having a nonzero value, as the datasheet also
* says that these registers are indeterminate after a reset!
* So, we explicitly set the low byte to a non-zero value first;
* then we can safely load the high byte, and then the correct
* value for the low byte, without the result ever being zero.
*/
/*
* Program the remaining device registers as required
*/
}
/*
* Higher-level setup & teardown
*/
static void
{
}
static int
{
caddr_t p;
int nregs;
int err;
nregs = 0;
switch (nregs) {
default:
case 1:
/*
* regset 0 represents the SIO operating registers
*/
rmc_comm_dev_acc_attr, &h);
if (err != DDI_SUCCESS)
return (EIO);
break;
case 0:
/*
* If no registers are defined, succeed vacuously;
* commands will be accepted, but we fake the accesses.
*/
break;
}
/*
* Now that the registers are mapped, we can initialise the SIO h/w
*/
return (0);
}
/*
* Initialization of the serial device (data structure, mutex, cv, hardware
* and so on). It is called from the attach routine.
*/
int
{
int err = DDI_SUCCESS;
/*
* Online the hardware ...
*/
if (err != 0)
return (-1);
/*
* call ddi_get_soft_iblock_cookie() to retrieve the
* the interrupt block cookie so that the mutexes are initialized
* before adding the interrupt (to avoid a potential race condition).
*/
if (err != DDI_SUCCESS)
return (-1);
if (err != DDI_SUCCESS)
return (-1);
/*
*/
/*
* Install soft and hard interrupt handler(s)
*
* the soft intr. handler will need the data protocol lock (dp_mutex)
* So, data protocol mutex and iblock cookie are created/initialized
* here
*/
if (err != DDI_SUCCESS) {
return (-1);
}
/*
* hardware interrupt
*/
/*
* did we successfully install the h/w interrupt handler?
*/
if (err != DDI_SUCCESS) {
return (-1);
}
}
/*
* Start cyclic callbacks
*/
return (0);
}
/*
* Termination of the serial device (data structure, mutex, cv, hardware
* and so on). It is called from the detach routine.
*/
void
{
}
}
/*
*/
/*
* Clean up on detach or failure of attach
*/
static void
{
/*
* disable interrupts now
*/
/*
* driver interface termination (if it has been initialized)
*/
if (drvi_init)
/*
* data protocol termination (if it has been initialized)
*/
if (dp_init)
/*
* serial device termination (if it has been initialized)
*/
if (sd_init)
}
}
/*
* Autoconfiguration routines
*/
static int
{
int instance;
/*
* only allow one instance
*/
if (instance != 0)
return (DDI_FAILURE);
switch (cmd) {
default:
return (DDI_FAILURE);
case DDI_RESUME:
"rmc_comm_attach")) == NULL)
return (DDI_FAILURE); /* this "can't happen" */
(void) tod_ops.tod_set_watchdog_timer(0);
}
"current_sgn", 0);
if ((current_sgn_p != NULL) &&
}
return (DDI_SUCCESS);
case DDI_ATTACH:
break;
}
/*
* Allocate the soft-state structure
*/
return (DDI_FAILURE);
NULL) {
return (DDI_FAILURE);
}
/*
* Set various options from .conf properties
*/
"baud-rate", 0);
"debug", 0);
/*
* the baud divisor factor tells us how to scale the result of
* the SIO_BAUD_TO_DIVISOR macro for platforms which do not
* use the standard 24MHz uart clock
*/
/*
* try to be reasonable if the scale factor contains a silly value
*/
/*
* initialize serial device
*/
return (DDI_FAILURE);
}
/*
* initialize data protocol
*/
/*
* initialize driver interface
*/
if (rmc_comm_drvintf_init(rcs) != 0) {
return (DDI_FAILURE);
}
/*
* Initialise devinfo-related fields
*/
/*
* enable interrupts now
*/
/*
* All done, report success
*/
return (DDI_SUCCESS);
}
static int
{
struct rmc_comm_state *rcs;
int instance;
return (DDI_FAILURE); /* this "can't happen" */
switch (cmd) {
case DDI_SUSPEND:
if (watchdog_enable && watchdog_activated &&
watchdog_was_active = 1;
(void) tod_ops.tod_clear_watchdog_timer();
} else {
watchdog_was_active = 0;
}
return (DDI_SUCCESS);
case DDI_DETACH:
/*
* reject detach if any client(s) still registered
*/
if (rcs->n_registrations != 0) {
return (DDI_FAILURE);
}
/*
* Committed to complete the detach;
* mark as no longer attached, to prevent new clients
* registering (as part of a coincident attach)
*/
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*ARGSUSED*/
static int
{
struct rmc_comm_state *rcs;
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
/*
* System interface structures
*/
static struct dev_ops rmc_comm_dev_ops =
{
0, /* refcount */
nodev, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
rmc_comm_attach, /* attach */
rmc_comm_detach, /* detach */
rmc_comm_reset, /* reset */
nulldev /* power() */
};
{
"rmc_comm driver, v%I%",
};
static struct modlinkage modlinkage =
{
{
&modldrv,
}
};
/*
* Dynamic loader interface code
*/
int
_init(void)
{
int err;
sizeof (struct rmc_comm_state), 0);
if (err == DDI_SUCCESS)
}
if (err != DDI_SUCCESS)
return (err);
}
int
{
}
int
_fini(void)
{
int err;
}
return (err);
}