* Copyright (c) 1988 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
* @(#)hd.c 7.4 (Berkeley) 5/1/89
* 9/8/89 (bostic) patched to include 7.6-7.8 but not 7.5
#include "../tahoe/cpu.h"
#include "../tahoe/mtpr.h"
#include "../tahoevba/vbavar.h"
#include "../tahoevba/hdreg.h"
#define hdunit(dev) (minor(dev)>>3)
#define hdpart(dev) (minor(dev)&0x07)
#define hdminor(unit, part) (((unit)<<3)|(part))
struct vba_ctlr
*hdcminfo
[NHDC
];
struct vba_device
*hddinfo
[NHD
];
int hdcprobe(), hdslave(), hdattach(), hddgo(), hdstrategy();
struct vba_driver hdcdriver
=
{ hdcprobe
, hdslave
, hdattach
, hddgo
, hdstd
, "hd", hddinfo
, "hdc", hdcminfo
};
#define HDC_INIT 0x01 /* controller initialized */
#define HDC_STARTED 0x02 /* start command issued */
#define HDC_LOCKED 0x04 /* locked for direct controller access */
#define HDC_WAIT 0x08 /* someone needs direct controller access */
u_short hdc_wticks
; /* timeout */
struct master_mcb
*hdc_mcbp
; /* address of controller mcb */
struct registers
*hdc_reg
; /* base address of i/o regs */
struct vb_buf hdc_rbuf
; /* vba resources */
struct master_mcb hdc_mcb
; /* controller mcb */
#define HDCMAXTIME 20 /* max time for operation, sec. */
#define HDCINTERRUPT 0xf0 /* interrupt vector */
* Per-drive state; probably everything should be "hd_", not "dk_",
* but it's not worth it, and dk is a better mnemonic for disk anyway.
u_short dk_def_cyl
; /* definition track cylinder address */
int dk_state
; /* open fsm */
u_short dk_bshift
; /* shift for * (DEV_BSIZE / sectorsize) XXX */
int dk_wlabel
; /* if label sector is writeable */
u_long dk_copenpart
; /* character units open on this drive */
u_long dk_bopenpart
; /* block units open on this drive */
u_long dk_openpart
; /* all units open on this drive */
int dk_ctlr
; /* controller# */
int dk_format
; /* if format program is using disk */
struct buf dk_utab
; /* i/o queue header */
struct disklabel dk_label
; /* disklabel for this disk */
struct mcb dk_mcb
; /* disk mcb */
* Drive states. Used during steps of open/initialization.
* States < OPEN (> 0) are transient, during an open operation.
* OPENRAW is used for unlabeled disks, to allow format operations.
#define CLOSED 0 /* disk is closed */
#define WANTOPEN 1 /* open requested, not started */
#define WANTOPENRAW 2 /* open requested, no label */
#define RDLABEL 3 /* reading pack label */
#define OPEN 4 /* intialized and ready */
#define OPENRAW 5 /* open, no label */
int hdcwstart
, hdcwatch();
/* see if the controller is really there, if so, init it. */
/* register */ struct vba_ctlr
*vm
;
register int br
, cvec
; /* must be r12, r11 */
register struct hdcsoftc
*hdc
;
static struct module_id id
;
/* initialize the hdc controller structure. */
hdc
= &hdcsoftc
[vm
->um_ctlr
];
if (!vbmemalloc(1, reg
, &dummypte
, &putl
)) {
printf("hdc%d: vbmemalloc failed.\n", vm
->um_ctlr
);
hdc
->hdc_reg
= (struct registers
*)putl
;
* try and ping the MID register; side effect of wbadaddr is to read
* the module id; the controller is bad if it's not an hdc, the hdc's
* writeable control store is not loaded, or the hdc failed the
* functional integrity test;
if (wbadaddr(&hdc
->hdc_reg
->module_id
, 4,
vtoph((struct process
*)NULL
, &id
)))
if (id
.module_id
!= (u_char
)HDC_MID
) {
printf("hdc%d: bad module id; id = %x.\n",
vm
->um_ctlr
, id
.module_id
);
if (id
.code_rev
== (u_char
)0xff) {
printf("hdc%d: micro-code not loaded.\n", vm
->um_ctlr
);
if (id
.fit
!= (u_char
)0xff) {
printf("hdc%d: FIT test failed.\n", vm
->um_ctlr
);
/* reset that pup; flag as inited */
hdc
->hdc_reg
->soft_reset
= 0;
hdc
->hdc_flags
|= HDC_INIT
;
/* allocate page tables and i/o buffer. */
if (!vbainit(&hdc
->hdc_rbuf
, MAXPHYS
, VB_32BIT
|VB_SCATTER
)) {
printf("hdc%d: vbainit failed\n", vm
->um_ctlr
);
/* set pointer to master control block */
(struct master_mcb
*)vtoph((struct proc
*)NULL
, &hdc
->hdc_mcb
);
br
= 0x17, cvec
= HDCINTERRUPT
+ vm
->um_ctlr
; /* XXX */
return(sizeof(struct registers
));
register struct mcb
*mcb
;
register struct disklabel
*lp
;
register struct dksoftc
*dk
;
static struct status status
;
dk
= &dksoftc
[vi
->ui_unit
];
dk
->dk_unit
= vi
->ui_unit
;
dk
->dk_ctlr
= vi
->ui_ctlr
;
mcb
->command
= HCMD_STATUS
;
mcb
->chain
[0].wcount
= sizeof(struct status
) / sizeof(long);
mcb
->chain
[0].memadr
= (u_long
)vtoph((struct process
*)0, &status
);
printf(" (no status)\n");
* Report the drive down if anything in the drive status looks bad.
* If the drive is offline and it is not on cylinder, then the drive
* is not there. If there is a fault condition, the hdc will try to
* clear it when we read the disklabel information.
if (!(status
.drs
&DRS_ONLINE
)) {
if (status
.drs
&DRS_ON_CYLINDER
)
printf(" (not online)\n");
if (status
.drs
&DRS_FAULT
)
printf(" (clearing fault)");
lp
->d_secsize
= status
.bytes_per_sec
;
lp
->d_nsectors
= status
.max_sector
+ 1;
lp
->d_ntracks
= status
.max_head
+ 1;
lp
->d_ncylinders
= status
.max_cyl
+ 1;
lp
->d_secpercyl
= lp
->d_ntracks
* lp
->d_nsectors
;
lp
->d_partitions
[0].p_offset
= 0;
lp
->d_partitions
[0].p_size
= LABELSECTOR
+ 1;
lp
->d_typename
[2] = '\0';
dk
->dk_def_cyl
= status
.def_cyl
;
register struct vba_device
*vi
;
register struct dksoftc
*dk
;
register struct disklabel
*lp
;
if (hdinit(hdminor(unit
, 0), 0)) {
printf(": unknown drive type");
if (dk
->dk_state
== OPEN
)
printf(": %s <secsize %d, ntrak %d, ncyl %d, nsec %d>",
lp
->d_typename
, lp
->d_secsize
, lp
->d_ntracks
,
lp
->d_ncylinders
, lp
->d_nsectors
);
* (60 / rpm) / (sectors per track * (bytes per sector / 2))
dk_mspw
[vi
->ui_dk
] = 120.0 /
(lp
->d_rpm
* lp
->d_nsectors
* lp
->d_secsize
);
addswap(makedev(HDMAJOR
, hdminor(unit
, 0)), lp
);
register struct disklabel
*lp
;
register struct dksoftc
*dk
;
register struct partition
*pp
;
int s
, error
, part
= hdpart(dev
), mask
= 1 << part
;
if (unit
>= NHD
|| (vi
= hddinfo
[unit
]) == 0 || vi
->ui_alive
== 0)
while (dk
->dk_state
!= OPEN
&& dk
->dk_state
!= OPENRAW
&&
sleep((caddr_t
)dk
, PZERO
+1);
if (dk
->dk_state
!= OPEN
&& dk
->dk_state
!= OPENRAW
)
if (error
= hdinit(dev
, flags
))
timeout(hdcwatch
, (caddr_t
)0, hz
);
* Warn if a partion is opened that overlaps another partition
* which is open unless one is the "raw" partition (whole disk).
#define RAWPART 8 /* 'x' partition */ /* XXX */
if ((dk
->dk_openpart
& mask
) == 0 && part
!= RAWPART
) {
pp
= &lp
->d_partitions
[part
];
end
= pp
->p_offset
+ pp
->p_size
;
for (pp
= lp
->d_partitions
;
pp
< &lp
->d_partitions
[lp
->d_npartitions
]; pp
++) {
if (pp
->p_offset
+ pp
->p_size
<= start
||
if (pp
- lp
->d_partitions
== RAWPART
)
if (dk
->dk_openpart
& (1 << (pp
- lp
->d_partitions
)))
"hd%d%c: overlaps open partition (%c)\n",
pp
- lp
->d_partitions
+ 'a');
if (part
>= lp
->d_npartitions
)
dk
->dk_copenpart
|= mask
;
dk
->dk_bopenpart
|= mask
;
register struct dksoftc
*dk
;
dk
= &dksoftc
[hdunit(dev
)];
dk
->dk_copenpart
&= ~mask
;
dk
->dk_bopenpart
&= ~mask
;
if (((dk
->dk_copenpart
| dk
->dk_bopenpart
) & mask
) == 0)
dk
->dk_openpart
&= ~mask
;
* Should wait for i/o to complete on this partition
* even if others are open, but wait for work on blkflush().
if (dk
->dk_openpart
== 0) {
while (dk
->dk_utab
.b_actf
)
sleep((caddr_t
)dk
, PZERO
-1);
register struct dksoftc
*dk
;
register struct disklabel
*lp
;
char *msg
, *readdisklabel();
vi
= hddinfo
[unit
= hdunit(dev
)];
dk
->dk_unit
= vi
->ui_slave
;
dk
->dk_ctlr
= vi
->ui_ctlr
;
if (msg
= readdisklabel(dev
, hdstrategy
, lp
)) {
log(LOG_ERR
, "hd%d: %s\n", unit
, msg
);
if (!(error
= hdreadgeometry(dk
)))
register struct dksoftc
*dk
;
* Calculate scaling shift for mapping
* DEV_BSIZE blocks to drive sectors.
mul
= DEV_BSIZE
/ lp
->d_secsize
;
register struct vba_device
*vi
;
register struct disklabel
*lp
;
register struct dksoftc
*dk
;
vi
= hddinfo
[unit
= hdunit(bp
->b_dev
)];
if (unit
>= NHD
|| vi
== 0 || vi
->ui_alive
== 0) {
if (dk
->dk_state
!= OPEN
&& (bp
->b_flags
& B_READ
) == 0) {
part
= hdpart(bp
->b_dev
);
if ((dk
->dk_openpart
& (1 << part
)) == 0) {
sz
= (bp
->b_bcount
+ lp
->d_secsize
- 1) / lp
->d_secsize
;
maxsz
= lp
->d_partitions
[part
].p_size
;
sn
= bp
->b_blkno
<< dk
->dk_bshift
;
if (sn
+ lp
->d_partitions
[part
].p_offset
<= LABELSECTOR
&&
sn
+ lp
->d_partitions
[part
].p_offset
+ sz
> LABELSECTOR
&&
(bp
->b_flags
& B_READ
) == 0 && dk
->dk_wlabel
== 0) {
if (sn
< 0 || sn
+ sz
> maxsz
) {
bp
->b_resid
= bp
->b_bcount
;
bp
->b_bcount
= sz
* lp
->d_secsize
;
bp
->b_cylin
= (sn
+ lp
->d_partitions
[part
].p_offset
) / lp
->d_secpercyl
;
if (!vi
->ui_mi
->um_tab
.b_active
)
register struct vba_device
*vi
;
register struct buf
*bp
, *dp
;
register struct vba_ctlr
*vm
;
register struct dksoftc
*dk
;
dk
= &dksoftc
[vi
->ui_unit
];
/* if queue empty, nothing to do. impossible? */
/* place on controller transfer queue */
if (vm
->um_tab
.b_actf
== NULL
)
vm
->um_tab
.b_actl
->b_forw
= dp
;
register struct vba_ctlr
*vm
;
register struct dksoftc
*dk
;
register struct disklabel
*lp
;
register struct master_mcb
*master
;
register struct mcb
*mcb
;
/* pull a request off the controller queue */
if ((dp
= vm
->um_tab
.b_actf
) == NULL
)
vm
->um_tab
.b_actf
= dp
->b_forw
;
/* mark controller active */
vi
= hddinfo
[hdunit(bp
->b_dev
)];
dk
= &dksoftc
[vi
->ui_unit
];
sn
= bp
->b_blkno
<< dk
->dk_bshift
;
mcb
->command
= (bp
->b_flags
& B_READ
) ? HCMD_READ
:HCMD_WRITE
;
/* assumes partition starts on cylinder boundary */
mcb
->head
= (sn
/ lp
->d_nsectors
) % lp
->d_ntracks
;
mcb
->sector
= sn
% lp
->d_nsectors
;
mcb
->drive
= vi
->ui_slave
;
/* mcb->context = 0; /* what do we want on interrupt? */
hdc
= &hdcsoftc
[vm
->um_ctlr
];
if (!hd_sgsetup(bp
, &hdc
->hdc_rbuf
, mcb
->chain
)) {
mcb
->chain
[0].wcount
= (bp
->b_bcount
+3) >> 2;
vbasetup(bp
, &hdc
->hdc_rbuf
, (int)lp
->d_secsize
);
dk_wds
[vi
->ui_dk
] += bp
->b_bcount
>>6;
master
->mcw
= MCL_QUEUED
;
master
->interrupt
= HDCINTERRUPT
+ vm
->um_ctlr
;
master
->forw_phaddr
= (u_long
)vtoph((struct proc
*)NULL
, mcb
);
hdc
->hdc_reg
->master_mcb
= (u_long
)hdc
->hdc_mcbp
;
* Wait for controller to finish current operation
* so that direct controller accesses can be done.
register struct vba_ctlr
*vm
= hdcminfo
[ctlr
];
register struct hdcsoftc
*hdc
;
while (vm
->um_tab
.b_active
|| hdc
->hdc_flags
& HDC_LOCKED
) {
hdc
->hdc_flags
|= HDC_WAIT
;
sleep((caddr_t
)hdc
, PRIBIO
);
hdc
->hdc_flags
|= HDC_LOCKED
;
* Continue normal operations after pausing for
* munging the controller directly.
register struct vba_ctlr
*vm
;
register struct hdcsoftc
*hdc
= &hdcsoftc
[ctlr
];
hdc
->hdc_flags
&= ~HDC_LOCKED
;
if (hdc
->hdc_flags
& HDC_WAIT
) {
hdc
->hdc_flags
&= ~HDC_WAIT
;
register struct buf
*bp
, *dp
;
register struct vba_ctlr
*vm
;
register struct vba_device
*vi
;
register struct hdcsoftc
*hdc
;
register struct mcb
*mcb
;
struct master_mcb
*master
;
uncache(&master
->context
);
if (!vm
->um_tab
.b_active
|| !(master
->mcs
&MCS_DONE
)) {
printf("hd%d: stray interrupt\n", ctlr
);
vi
= hddinfo
[hdunit(bp
->b_dev
)];
dk
= &dksoftc
[vi
->ui_unit
];
dk_busy
&= ~(1<<vi
->ui_dk
);
timedout
= (hdc
->hdc_wticks
>= HDCMAXTIME
);
if (master
->mcs
& (MCS_SOFTERROR
| MCS_FATALERROR
) || timedout
)
hdcerror(ctlr
, *(u_long
*)master
->xstatus
);
if (vm
->um_tab
.b_active
) {
vm
->um_tab
.b_actf
= dp
->b_forw
;
dp
->b_actf
= bp
->av_forw
;
vbadone(bp
, &hdc
->hdc_rbuf
);
/* start up now, if more work to do */
else if (dk
->dk_openpart
== 0)
/* if there are devices ready to transfer, start the controller. */
if (hdc
->hdc_flags
& HDC_WAIT
) {
hdc
->hdc_flags
&= ~HDC_WAIT
;
} else if (vm
->um_tab
.b_actf
)
hdioctl(dev
, cmd
, data
, flag
)
register struct dksoftc
*dk
;
register struct disklabel
*lp
;
*(struct disklabel
*)data
= *lp
;
((struct partinfo
*)data
)->disklab
= lp
;
((struct partinfo
*)data
)->part
=
&lp
->d_partitions
[hdpart(dev
)];
if ((flag
& FWRITE
) == 0)
error
= setdisklabel(lp
, (struct disklabel
*)data
,
(dk
->dk_state
== OPENRAW
) ? 0 : dk
->dk_openpart
);
if (error
== 0 && dk
->dk_state
== OPENRAW
)
if ((flag
& FWRITE
) == 0)
dk
->dk_wlabel
= *(int *)data
;
if ((flag
& FWRITE
) == 0)
else if ((error
= setdisklabel(lp
, (struct disklabel
*)data
,
(dk
->dk_state
== OPENRAW
) ? 0 : dk
->dk_openpart
)) == 0) {
if (error
== 0 && dk
->dk_state
== OPENRAW
)
/* simulate opening partition 0 so write succeeds */
dk
->dk_openpart
|= (1 << 0); /* XXX */
error
= writedisklabel(dev
, hdstrategy
, lp
);
dk
->dk_openpart
= dk
->dk_copenpart
| dk
->dk_bopenpart
;
* Watch for lost interrupts.
register struct hdcsoftc
*hdc
;
register struct vba_ctlr
**vmp
;
timeout(hdcwatch
, (caddr_t
)0, hz
);
for (vmp
= hdcminfo
, hdc
= hdcsoftc
, ctlr
= 0; ctlr
< NHDC
;
if (*vmp
== 0 || (*vmp
)->um_alive
== 0)
if ((*vmp
)->um_tab
.b_active
&&
hdc
->hdc_wticks
++ >= HDCMAXTIME
) {
printf("hd%d: lost interrupt\n", ctlr
);
register int unit
= hdunit(dev
);
register struct dksoftc
*dk
;
if (unit
>= NHD
|| (vi
= hddinfo
[unit
]) == 0 || vi
->ui_alive
== 0 ||
(dk
= &dksoftc
[unit
])->dk_state
!= OPEN
)
return ((int)lp
->d_partitions
[hdpart(dev
)].p_size
>> dk
->dk_bshift
);
register struct dksoftc
*dk
;
register struct master_mcb
*master
;
register struct mcb
*mcb
;
register struct hdcsoftc
*hdc
;
mcb
->drive
= dk
->dk_unit
;
hdc
= &hdcsoftc
[dk
->dk_ctlr
];
master
->mcw
= MCL_IMMEDIATE
;
master
->forw_phaddr
= (u_long
)vtoph((struct proc
*)NULL
, mcb
);
/* kick controller and wait */
hdc
->hdc_reg
->master_mcb
= (u_long
)hdc
->hdc_mcbp
;
for (timeout
= 15000; timeout
; --timeout
) {
if (master
->mcs
&MCS_FATALERROR
) {
printf("hdc%d: fatal error\n", dk
->dk_ctlr
);
hdcerror(dk
->dk_ctlr
, *(u_long
*)master
->xstatus
);
if (master
->mcs
&MCS_DONE
)
printf("hdc%d: timed out\n", dk
->dk_ctlr
);
printf("hd%d: error %lx\n", ctlr
, code
);
static geometry_sector geometry
;
register struct mcb
*mcb
;
register struct disklabel
*lp
;
* Read the geometry block (at head = 0 sector = 0 of the drive
* definition cylinder), validate it (must have the correct version
* number, header, and checksum).
mcb
->command
= HCMD_READ
;
mcb
->cyl
= dk
->dk_def_cyl
;
mcb
->chain
[0].wcount
= sizeof(geometry_sector
) / sizeof(long);
mcb
->chain
[0].memadr
= (u_long
)vtoph((struct process
*)0, &geometry
);
/* mcb->chain[0].memadr = (long)&geometry; */
printf("hd%d: can't read default geometry.\n", dk
->dk_unit
);
geo
= &geometry
.geometry_block
;
if (geo
->version
> 64000 || geo
->version
< 0) {
printf("hd%d: bad default geometry version#.\n", dk
->dk_unit
);
if (bcmp(&geo
->id
[0], GB_ID
, GB_ID_LEN
)) {
printf("hd%d: bad default geometry header.\n", dk
->dk_unit
);
if (geometry
.checksum
!= cnt
) {
printf("hd%d: bad default geometry checksum.\n", dk
->dk_unit
);
/* 1K block in Harris geometry; convert to sectors for disklabels */
for (cnt
= 0; cnt
< GB_MAXPART
; cnt
++) {
lp
->d_partitions
[cnt
].p_offset
=
geo
->partition
[cnt
].start
* (1024 / lp
->d_secsize
);
lp
->d_partitions
[cnt
].p_size
=
geo
->partition
[cnt
].length
* (1024 / lp
->d_secsize
);
lp
->d_npartitions
= GB_MAXPART
;