pcide.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* ATA disk driver
*
* Handles the standard PCMCIA ATA interface
*
*/
#include <sys/sysmacros.h>
#include <sys/dditypes.h>
/*
* PCMCIA and DDI related header files
*/
int pcata_debug = 0;
ata_soft_t *softp);
#ifdef ATA_DEBUG
static void pcata_print_sttflag(int svalue);
#endif
struct cb_ops pcata_cb_ops = {
pcata_open, /* driver open routine */
pcata_close, /* driver close routine */
pcata_strategy, /* driver strategy routine */
pcata_print, /* driver print routine */
pcata_dump, /* driver dump routine */
pcata_read, /* driver read routine */
pcata_write, /* driver write routine */
pcata_ioctl, /* driver ioctl routine */
nodev, /* driver devmap routine */
nodev, /* driver mmap routine */
nodev, /* driver segmap routine */
nochpoll, /* driver chpoll routine */
pcata_prop_op, /* driver prop_op routine */
0, /* driver cb_str - STREAMS only */
};
DEVO_REV, /* devo_rev, */
0, /* refcnt */
pcata_getinfo, /* info */
nulldev, /* identify */
nulldev, /* probe */
pcata_attach, /* attach */
pcata_detach, /* detach */
nulldev, /* reset */
&pcata_cb_ops, /* driver operations */
NULL, /* bus operations */
NULL, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
void *pcata_soft = NULL;
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. This one is a driver */
"PCMCIA ATA disk controller",
&ata_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
};
int
_init(void)
{
int status;
if (status)
return (status);
return (status);
}
int
_fini(void)
{
int status;
if (!status)
return (status);
}
int
{
}
static int
{
int ret;
/* resume from a checkpoint */
if (cmd == DDI_RESUME)
return (DDI_SUCCESS);
if (cmd != DDI_ATTACH) {
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
/* Allocate soft state associated with this instance. */
if (ret != DDI_SUCCESS) {
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
#ifdef ATA_DEBUG
if (pcata_debug & DINIT)
#endif
softp->card_state = 0;
softp->intr_pending = 0;
softp->softint_pending = 0;
softp->write_in_progress = 0;
softp->ejected_while_mounted = 0;
/*
* Initialize to 0 until it is incremented in pcram_check_media
*/
softp->checkmedia_flag = 0;
softp->ejected_media_flag = 0;
/*
* if attach fails, Solaris won't call detach
* so we call detach here to release all flagged resources
*/
if (ret == DDI_FAILURE) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
{
int ret;
/*
* create ata_mutex
*/
&softp->soft_blk_cookie);
if (ret != DDI_SUCCESS) {
#ifdef ATA_DEBUG
#endif
return (ret);
}
/*
* Setup the mutexii and condition variables.
* Initialize the mutex that protects the ATA registers.
*/
(void *)(softp->soft_blk_cookie));
/* for DKIOCSTATE ioctl() */
/*
* link in soft interrupt
*/
if (ret != DDI_SUCCESS) {
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
/*
* Register with Card Services
*/
if (ret != CS_SUCCESS) {
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
*(client_reg.iblk_cookie));
/*
* Get logical socket number and store in softp struct
*/
if (ret != CS_SUCCESS) {
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
/*
*
*/
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
/*
* After the RequestSocketMask call, we start receiving events
*/
if (ret != CS_SUCCESS) {
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
/*
* We may not get the CARD_READY event
* until after we leave this function.
*/
/* wait for drive to be initialized */
(void) pcata_readywait(softp);
}
}
/*
* Wait for minor node creation before returning
*/
/*
* print banner to announce device
*/
return (DDI_SUCCESS);
}
static int
{
int ret;
if (cmd == DDI_SUSPEND)
return (DDI_SUCCESS);
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
/*
* Call the card_removal routine to do any final card cleanup
*/
if (CARD_PRESENT_VALID(softp)) {
}
/*
* Release our socket mask - note that we can't do much
* if we fail these calls other than to note that
* the system will probably panic shortly. Perhaps
* we should fail the detach in the case where these
* CS calls fail?
*/
if (ret != CS_SUCCESS) {
#ifdef ATA_DEBUG
"ReleaseSocketMask failed %s\n",
#endif
}
}
/*
* Deregister with Card Services - we will stop getting
* events at this point.
*/
if (ret != CS_SUCCESS) {
#ifdef ATA_DEBUG
"DeregisterClient failed %s\n",
#endif
return (DDI_FAILURE);
}
}
/* unregister the softintrrupt handler */
}
/*
*/
/* for DKIOCSTATE ioctl() */
}
/* Free various structures and memory here. */
/* Free the soft state structure here */
#ifdef ATA_DEBUG
if (pcata_debug & DPCM)
#endif
return (DDI_SUCCESS);
}
/*
* Common controller object interface
*/
/*
* initiate a new I/O request
* either start it or add it to the request queue
*/
int
{
int ret;
int kf = 0;
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
(void *)unitp,
(void *)bp,
}
#endif
if (!CARD_PRESENT_VALID(softp))
return (CTL_SEND_FAILURE);
return (CTL_SEND_FAILURE);
}
if (!pktp) {
return (CTL_SEND_FAILURE);
}
#ifdef ATA_DEBUG
if (pcata_debug & DENT)
#endif
} else {
}
/*
* b_private is set to 0xBEE by pcata_buf_setup
* which is called by an ioctl DIOCTL_RWCMD with a subcommand
* of either DADKIO_RWCMD_READ or DADKIO_RWCMD_WRITE
*
* these commands are used to do I/O through the IOCTL interface
*
* b_back contains a pointer to the ioctl packet struct (dadkio_rwcmd)
*/
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
pktp->cp_passthru);
}
#endif
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
}
#endif
/*
* The controller is idle.
* Put the packet in ab_active....
*/
/*
* ... and start it off
*/
while (ret == PCATA_GO_RETRY) {
}
if (ret == DDI_FAILURE) {
return (CTL_SEND_FAILURE);
}
} else {
/*
* the controller is busy now so put the packet
* on ab_head or ab_last.
*/
else
}
return (CTL_SEND_SUCCESS);
}
/*
* initiate I/O for packet linked on ab_active
*/
static int
{
return (DDI_SUCCESS);
if (!CARD_PRESENT_VALID(softp)) {
return (DDI_FAILURE);
}
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
(void *)unitp,
}
#endif
/*
* calculate drive address based on pktp->cp_srtsec
*/
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
"_go %s at lba=%d (%uc %uh %us) "
"%d sectors cmd=%x ctl=%x\n",
}
#endif
return (DDI_FAILURE);
/*
* the command should make the controller status show BSY
* the ISR intr_hi will not record status while the controller is BSY
* therefore set interrupt expected state now
* the next time we receive an interrupt and the controller is not BSY
* the ISR will do the right thing
*/
softp->intr_pending++;
/*
* If there's data to go along with the command, send it now.
*/
return (DDI_FAILURE);
} else {
"_go at lba=%d (%uc %uh %us) "
"%d sectors cmd=%x ctl=%x \n",
return (PCATA_GO_RETRY);
}
}
}
return (DDI_SUCCESS);
}
/*
* return value
* success means go on o next block
* failure means continue with current block
*/
static void
{
int nbytes;
int ret;
if (!CARD_PRESENT_VALID(softp)) {
return;
}
/*
* If there was an error, quit now
*/
#ifdef ATA_DEBUG
if (pcata_debug & DIO)
"_iocmpl I/O error status=%04x error=%04x\n",
error);
#endif
return;
}
/*
* do the read of the block
*/
if (ret == DDI_FAILURE) {
/*
* If the controller never presented the data
* and the error bit isn't set,
* there's a real problem. Kill it now.
*/
return;
}
}
/*
* update counts...
*/
/* If last command was a GET_DEFECTS delay a bit */
drv_usecwait(1000);
}
int
{
/*
* In ata there is no hardware support to tell if the interrupt
* belongs to ata or not. So no checks necessary here. Later
* will check the buffer and see if we have started a transaction.
*/
int rval = DDI_INTR_UNCLAIMED;
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
"_intr_hi sn=%d status=%x intr_pending=%d "
"softint_pending=%d wr_in_prog=%d\n",
}
#endif
/*
* this test is not redundant (don't remove it)
* it is part of card removal processing
* and prevents losing interrupt threads
*/
if (!CARD_PRESENT_VALID(softp)) {
return (rval);
}
/*
* this is a shared interrupt
* if the controller is NOT busy,
* and an interrupt is expected
*/
(softp->intr_pending > 0)) {
"?_intr_hi sn=%d status=%x\n",
/* handle aborted commands */
(softp->write_in_progress > 0)) {
softp->write_in_progress = 0;
}
}
(softp->write_in_progress == 0) &&
(softp->intr_pending > 0)) {
/*
* Read the status register,
* this clears an interrupt from the ata device
*/
softp->intr_pending--;
/*
* Make sure the interrupt is cleared, occasionally it is not
* cleared by the first status read.
*/
/* put the error status in the right place */
}
}
#ifdef ATA_DEBUG
if (pcata_debug & DENT)
"_intr_hi status=%x error=%x claimed=%d pending=%d\n",
(rval == DDI_INTR_CLAIMED),
#endif
if ((rval == DDI_INTR_CLAIMED) &&
softp->softint_pending++;
}
return (rval);
}
pcata_intr(char *parm)
{
int ret;
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
if (softp->softint_pending == 0) {
return (DDI_INTR_UNCLAIMED);
}
goto done;
}
/* perform I/O completion */
if (!CARD_PRESENT_VALID(softp))
goto done;
/*
* if packet is done (either errors or all bytes transfered)
* pktp points to current packet
* ab_active is cleared
* else
* pktp is null
* ab_active is unchanged
*/
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
" CPS_SUCCESS=%d pkpt=%p cp_resid = %d\n",
CPS_SUCCESS, (void *)pktp,
}
#endif
/*
* calculate drive address based on
* pktp->cp_srtsec
*/
"_intr %s at lba=%d (%uc %uh %us) "
"%d sectors cmd=%x ctl=%x\n",
"write" : "read",
pktp = 0;
} else {
/* I/O is complete or an error has occured */
}
} else {
/* I/O is still in progress */
pktp = 0;
}
}
/*
* packet which caused this interrupt is now complete
*/
if (pktp) {
#ifdef ATA_DEBUG
#endif
}
/* release the thread for the I/O just completed */
}
/* if ab_active is NULL attempt to dequeue next I/O request */
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
"_start_next_cmd current:%p head:%p\n",
}
#endif
}
softp->softint_pending--;
/* if ab_active is not NULL, attempt to initiate I/O */
while (ret == PCATA_GO_RETRY) {
}
}
goto exit;
done:
softp->softint_pending--;
exit:
#ifdef ATA_DEBUG
if (pcata_debug & DENT)
#endif
return (DDI_INTR_CLAIMED);
}
/*
* if all drives then eliminate tests for pktp->cp_ctl_private == unitp
* if single drive then examine usage of flag ATA_OFFLINE
*/
static void
{
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
/*
* nack the active request
*/
/*
* now nack all queued requests
*/
}
/* release the thread for the I/O */
}
}
}
static void
{
#ifdef ATA_DEBUG
if (pcata_debug & DENT)
#endif
}
}
/*
* pcata_wait -- wait for a register of a controller to achieve a
* specific state. Arguments are a mask of bits we care about,
* and two sub-masks. To return normally, all the bits in the
* first sub-mask must be ON, all the bits in the second sub-
* mask must be OFF. If 5 seconds pass without the controller
* achieving the desired bit configuration, we return 1, else
* 0.
*/
static int
{
register int i;
return (0);
drv_usecwait(10);
}
#ifdef ATA_DEBUG
"sn=%d port=%x on: 0x%x off: 0x%x ival: 0x%x eval: 0x%x\n",
#endif
return (1);
}
/*
* Similar to pcata_wait but the timeout is much shorter. It is only used
* during initialization when long delays are noticable.
*/
static int
{
register int i;
return (0);
drv_usecwait(10);
}
return (1);
}
/*
* Wait until the command interrupt has been serviced before starting
* another command.
*
*/
static void
{
int i;
for (i = 0; i < PCATA_WAIT_CNT &&
drv_usecwait(10);
}
}
static int
{
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
softp->write_in_progress = 0;
return (DDI_FAILURE);
}
/*
* copy count bytes from pktp->v_addr to the data port
*/
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
count);
}
#endif
if (!CARD_PRESENT_VALID(softp)) {
softp->write_in_progress = 0;
return (DDI_FAILURE);
}
if (softp->write_in_progress > 0)
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
}
#endif
return (DDI_SUCCESS);
}
static int
{
return (DDI_FAILURE);
}
/*
* copy count bytes from the data port to pktp->ac_v_addr
*/
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
}
#endif
if (!CARD_PRESENT_VALID(softp))
return (DDI_FAILURE);
#ifdef ATA_DEBUG
if (pcata_debug & DIO)
#endif
return (DDI_SUCCESS);
}
int
{
char buf[41];
int i;
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
/* toggle reset bit to trigger a software reset */
if (!(CARD_PRESENT_VALID(softp)))
return (DDI_FAILURE);
drv_usecwait(1000);
if (!(CARD_PRESENT_VALID(softp)))
return (DDI_FAILURE);
/*
* The interrupt disable command does not work reliably with
* all PC ATA cards. It is better to leave interupts enabled
* and process them as they occur.
*/
if (!secbuf) {
return (DDI_FAILURE);
}
(sizeof (struct atarpbuf) +
sizeof (struct scsi_inquiry)), KM_NOSLEEP))) {
return (DDI_FAILURE);
}
/*
* load up with the drive number
*/
if (drive == 0) {
} else {
}
sizeof (struct scsi_inquiry)));
continue;
}
dcount++;
sizeof (struct scsi_inquiry)));
dcount--;
break;
}
/*
* We need to swap the strings on both platforms.
*/
#ifdef _BIG_ENDIAN
#else
sizeof (rpbp->atarp_drvser));
#endif
#ifdef ATA_DEBUG
if (pcata_debug & DINIT) {
/* truncate model */
i--) {
buf[i] = '\0';
}
buf,
" cap 0x%x\n",
" advpiomode 0x%x\n",
}
#endif
if (!(CARD_PRESENT_VALID(softp)))
return (DDI_FAILURE);
}
if (dcount == 0)
return (DDI_FAILURE);
continue; /* no drive here */
}
continue;
}
/*
* feed some of the info back in a set_params call.
*/
== DDI_FAILURE) {
/*
* there should have been a drive here but it
* didn't respond properly. It stayed BUSY.
*/
(sizeof (struct atarpbuf) +
sizeof (struct scsi_inquiry)));
}
continue;
}
dcount++;
}
#ifdef ATA_DEBUG
if (pcata_debug)
#endif
}
/*
* pcata_drive_type()
*/
static uchar_t
{
if (pcata_wait1(AT_ALTSTATUS,
return (ATA_DEV_NONE);
/*
* note: pcata_drive_type is only called by pcata_getedt()
*/
/* command also known as IDENTIFY DEVICE */
softp->intr_pending++;
#ifdef ATA_DEBUG
if (pcata_debug) {
}
#endif
return (ATA_DEV_NONE);
}
#ifdef ATA_DEBUG
if (pcata_debug) {
sizeof (rpbp->atarp_model));
rpbp->atarp_model);
sizeof (rpbp->atarp_model));
} else {
}
}
#endif
/*
* wait for the drive to recognize I've read all the data. some
* drives have been observed to take as much as 3msec to finish
* sending the data; allow 5 msec just in case.
*/
return (ATA_DEV_NONE);
if (!CARD_PRESENT_VALID(softp))
return (ATA_DEV_NONE);
return (ATA_DEV_NONE);
return (ATA_DEV_DISK);
}
/*
* Drive set params command.
*/
static int
{
#ifdef ATA_DEBUG
if (pcata_debug & DINIT)
#endif
if (!CARD_PRESENT_VALID(softp))
return (DDI_FAILURE);
return (DDI_FAILURE);
softp->intr_pending++;
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
void
pcata_byte_swap(char *buf, int n)
{
int i;
char c;
n &= ~1;
for (i = 0; i < n; i += 2) {
c = buf[i];
buf[i + 1] = c;
}
}
int
{
int i;
int laststat;
char size;
char accepted_size = -1;
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
if (!CARD_PRESENT_VALID(softp))
return (DDI_FAILURE);
/*
* says it doesn't understand them.
*/
/*
* set drive number
*/
return (DDI_FAILURE);
/*
* send the command
*/
softp->intr_pending++;
/*
* there should have been a drive here but it
* didn't respond properly. It stayed BUSY.
* complete failure!
*/
return (DDI_FAILURE);
/*
* Wait for DRDY or error status
*/
i++) {
break;
drv_usecwait(10);
}
if (i == ATA_LOOP_CNT)
/*
* Didn't get ready OR error... complete failure!
* there should have been a drive here but it
* didn't respond properly. It didn't set ERR or DRQ.
*/
return (DDI_FAILURE);
/*
* See if DRQ or error
*/
/*
* there should have been a drive here but it
* didn't respond properly. There was an error.
* Try the next value.
*/
continue;
}
/*
* Got ready.. use the value that worked.
*/
}
if (accepted_size == -1) {
/*
* None of the values worked...
* the controller responded correctly though so it probably
*/
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
"block factor to 1\n");
}
#endif
return (DDI_SUCCESS);
}
if (accepted_size == 1) {
/*
* OK... Leave it at 1
*/
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
return (DDI_SUCCESS);
}
accepted_size >>= 1;
/*
* Allow a user specified block factor to override the system chosen
* value. Only allow the user to reduce the value.
* -1 indicates the user didn't specify anything
*/
return (DDI_FAILURE);
softp->intr_pending++;
/*
* there should have been a drive here but it
* didn't respond properly. It stayed BUSY.
*/
return (DDI_FAILURE);
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
return (DDI_SUCCESS);
}
static int
{
void *instance;
&instance) != DDI_SUCCESS)
return (ENODEV);
if (!softp) {
return (ENXIO);
}
if (!CARD_PRESENT_VALID(softp))
return (ENODEV);
/*
* If pcata_strategy() encounters an exception, or card_removal
* is called, before this is complete, it is possible that
* biodone will be called but the buffer (bp) wont
* be released unless B_ASYNC flag is set. So
* don't set B_ASYNC flag unless you mean it.
*/
(void) pcata_strategy(bp);
for (;;) {
if (!CARD_PRESENT_VALID(softp))
return (ENODEV);
else
return (0);
}
drv_usecwait(1000);
}
}
/* ddi print */
static int
{
void *instance;
/* get instance number */
&instance) != DDI_SUCCESS) {
"return ENODEV\n");
return (ENODEV);
}
return (ENXIO);
}
return (0);
}
static int
{
}
/* ARGSUSED2 */
static int
{
}
/* ARGSUSED2 */
static int
{
}
void
{
void *instance;
&instance) != DDI_SUCCESS)
}
static void
{
#ifdef ATA_DEBUG
if (pcata_debug & DENT) {
}
#endif
/* check for error retry */
sec_count = 1;
} else {
/*
* Limit requetst to ab_max_transfer sectors.
* The value is specified by the user in the
* max_transfer property. It must be in the range 1 to 256.
* When max_transfer is 0x100 it is bigger than 8 bits.
* The spec says 0 represents 256 so it should be OK.
*/
}
#ifdef ATA_DEBUG
if (pcata_debug & DIO) {
"_iosetup: asking for start 0x%lx count 0x%x\n",
}
#endif
/*
* setup the task file registers
*/
if (pktp->cp_passthru) {
case DADKIO_RWCMD_READ:
break;
case DADKIO_RWCMD_WRITE:
break;
}
} else {
case DCMD_READ:
case DCMD_WRITE:
case DCMD_RECAL:
case DCMD_SEEK:
case DCMD_RDVER:
case DCMD_READ:
break;
case DCMD_WRITE:
break;
case DCMD_RECAL:
break;
case DCMD_SEEK:
break;
case DCMD_RDVER:
break;
}
break;
default:
"unrecognized cmd 0x%x\n",
break;
}
}
}
/* ARGSUSED */
int
{
if (!(CARD_PRESENT_VALID(softp)))
return (DDI_FAILURE);
return (DDI_FAILURE);
/* spin up the drive */
softp->intr_pending++;
if (pcata_wait(AT_ALTSTATUS,
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
/* set the R/W multiple value decided at first init time */
softp->intr_pending++;
/*
* there should have been a drive here but it
* didn't respond properly. It stayed BUSY.
*/
#ifdef ATA_DEBUG
#endif
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
#ifdef ATA_DEBUG
static char *
static void
{
int i;
char buf[80];
if (svalue & 0x80) {
}
}
}
#endif