Files
Arquivotheca.SunOS-4.1.3/sys/sun/mb_machdep.c
seta75D 2e8a93c394 Init
2021-10-11 18:20:23 -03:00

883 lines
21 KiB
C

#ifndef lint
static char sccsid[] = "@(#)mb_machdep.c 1.1 92/07/30 SMI";
#endif
/*
* Copyright (c) 1988-1991 by Sun Microsystems, Inc.
*/
/*
* Machine dependent mainbus support routines.
*
* N.B. The mainbus code no longer requires the mainbus structures (mb_ctlr
* and so on) to ask for DVMA space or to queue up when DVMA is not available.
* Drivers can now send in a generic map structure and a pointer to a function
* which is queued and invoked later when space frees up again. This mechanism
* allows devices to manage their own queues and especially to queue multiple
* I/O's to the same device. The old mbsetup()/mbrelse() interface has been
* retained and can be used in the same way as before (these routines can
* be found in <sundev/mb.c>).
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/vmmac.h>
#include <sys/vmmeter.h>
#include <sys/vmparam.h>
#include <sys/map.h>
#include <sys/buf.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/mman.h>
#include <sys/debug.h>
#include <sys/kmem_alloc.h>
#include <machine/mmu.h>
#include <machine/cpu.h>
#include <machine/psl.h>
#include <machine/pte.h>
#include <machine/reg.h>
#include <sundev/mbvar.h>
#include <vm/hat.h>
#include <vm/as.h>
#include <vm/page.h>
#include <vm/seg.h>
#include <machine/seg_kmem.h>
#ifdef IOMMU
#include <machine/iommu.h>
#endif IOMMU
#ifdef IOC
#include <machine/iocache.h>
#endif IOC
/*
* Drivers queue function pointers here when they have to wait for
* resources. Subsequent calls from the same driver that are forced to
* wait need not queue up again, as the function pointed to will run the
* driver's internal queues until done or space runs out again. The arg
* parameter can be used by drivers as a cookie to be passed back by the
* callback function. They should pass in NULL if not using the parameter.
* Note that if drivers keep calling the allocation routine after
* resources ran out that the arg parameter associated with subsequent
* calls is ignored.
*
* The system priority level at which the allocation routine is called at
* also indicates an implicit ordering as to whom is called back first.
* When a resource is freed, the current system priority level is used to
* screen out and not call back directly waiters who called the allocation
* routine at a lower system priority level- they instead are called via
* softcall() (if resources are left after calling back directly those who
* can be called back directly). Note that if the original call had stored
* up at priority 0, the intent of this indirect mechanism cannot be
* achieved (and a warning message will be printed upon the storing of
* such a callback).
*
* Because elements may be removed out of order (due to pri differences),
* a simple cycle through a limited array isn't enough if we still wan't
* preserve any semblance of FIFO servicing. A simple linked list will fit
* the bill. In fact, an array could still be used, but it turns out that
* a linked list uses fewer instructions to manage in this fashion (while
* losing locality of reference and possible cache gains). On the other
* hand, this is more like we'll want this to be, and it probably doesn't
* make that big a difference.
*/
#define MAXMBQLEN 10
static struct mbq_elem {
struct mbq_elem *mbq_next;
func_t mbq_funcp;
caddr_t mbq_arg;
int mbq_pri;
} mbq[MAXMBQLEN];
/*
* MB related stats
*/
static struct mbs {
int m_runout; /* times DVMA has run out */
int m_queued; /* driver already had request queued */
int m_bigreq; /* largest request received (bytes) */
int m_hiqlen; /* longest length of wait queue */
int m_softcall; /* number of callbacks via softcall */
} mbs;
static int mbqlen;
static struct mbq_elem *mbqfree, *mbwaitq;
static void mbq_store(); /* store element */
static void mbq_retrieve(); /* retrieve element */
static int mbq_softcall(); /* entry into callback via softcall */
/*
* run_mhq && mb_setup now in sundev/mb.c
*/
/*
* Allocate and setup Mainbus map registers. Flags says whether the
* caller can't wait (e.g. if the caller is at interrupt level). We
* also allow DMA to memory already mapped at the Mainbus (e.g., for
* Sun Ethernet board memory) if buscheck() says it's ok.
*
* We assume that the physical translations for the pages have already
* been locked down before we were called.
*/
/*
* New interface for mainbus resources. The main reason for this is to
* provide smart controllers with the ability to queue multiple I/Os to
* a single device, which was impossible with the old stuff. This is also
* a move towards cleaning up the complexity of the queueing structures
* both for DVMA resources and for autoconf.
*
* This routine accepts a function pointer which is queued if there is no
* no space available and the caller cannot sleep.
*/
int
mb_mapalloc(map, bp, flags, waitfp, arg)
register struct map *map;
register struct buf *bp;
int flags;
func_t waitfp;
caddr_t arg;
{
register int reg, s, o, npf;
struct mbcookie mbcookie;
#if defined(sun3x) || defined(sun4m)
struct pte pte;
#endif sun3x || sun4m
#ifdef sun2
struct ctx *ctx;
#endif sun2
#if defined(sun4m) && defined(IOMMU)
struct map *iom_choose_map();
extern int iom;
#endif defined(sun4m) && defined(IOMMU)
/*
* Size the xfer, rounding up a page if necessary.
*/
o = (int)bp->b_un.b_addr & PAGEOFFSET;
npf = mmu_btopr(bp->b_bcount + o);
reg = buscheck(bp);
if (reg < 0)
panic("mbsetup buscheck fail");
#ifndef sun4c
#if defined(sun4m) && defined(IOMMU)
if (reg > 0) {
/*
* If DVMA target is not main memory. Make
* sure they can be DVMA'ed.
*/
switch (bustype(reg)) {
case BT_SBUS:
if (map == mbutlmap) {
/*
* mbutlmap use only from m_clalloc
*/
return(0);
}
case BT_VIDEO:
/* go ahead to setup IOMMU */
break;
case BT_VME:
if (map == mb_hd.mh_map) {
/*
* VME-> VME.
*
* This does not involve IOC/IOMMU,
* just return VME slave address.
*/
mbcookie.mbi_mapreg = reg;
mbcookie.mbi_offset = o;
return (*(int *)&mbcookie);
} else {
/* Sbus to VME, not supported!! */
return(0);
}
case BT_UNKNOWN:
case BT_OBIO:
/* NO DVMA allowed to these spaces! */
return(0);
}
}
#else defined(sun4m) && defined(IOMMU)
/*
* If not OBMEM, for non-sun4c machines, return the
* cookie that buscheck makes up for VME addresses.
*
* For sun4c, we skip this and force a mapping across
* DVMA[] (I/O on sun4c always goes through MMU context
* 0- even SBus to SBus transactions).
*/
if (reg > 0) {
/*
* In contiguous mainbus memory,
* i.e., not on on-board memory
* (mainbus is historical).
*/
mbcookie.mbi_mapreg = reg;
mbcookie.mbi_offset = o;
return (*(int *)&mbcookie);
}
#endif defined(sun4m) && defined(IOMMU)
#endif sun4c
#if defined(sun4m) && defined(IOMMU)
/* see if we can use vme32map */
if ((map == mb_hd.mh_map) && (flags & MDR_VME32) &&
!(flags & MDR_DVMA_PEEK))
map = vme32map;
/* see if we can use bigsbusmap */
if ((map == dvmamap) && (flags & MDR_BIGSBUS))
map = bigsbusmap;
/* see if we can use altsbusmap */
if ((map == dvmamap) && (flags & MDR_ALTSBUS))
map = altsbusmap;
#endif defined(sun4m) && defined(IOMMU)
/*
* Else the request is for all type 0 memory which still needs
* to be mapped into DVMA space. Allocate virtual space for the
* mapping including one extra for redzone at end of mapping.
*/
s = splvm();
while ((reg = bp_alloc(map, bp, npf + 1)) == 0) {
/*
* No DVMA available. We either queue the function ptr
* or sleep, depending on the caller's state of mind.
*/
if (flags & MB_CANTWAIT) {
if (waitfp) {
mbq_store(waitfp, arg, spltoipl(s));
mbs.m_bigreq = MAX(ctob(npf), mbs.m_bigreq);
}
(void) splx(s);
return (0);
}
mapwant(map)++;
(void) sleep((caddr_t)map, PSWP);
}
(void) splx(s);
#ifdef sun2
ctx = mmu_getctx();
if (ctx != kctx)
mmu_setctx(kctx);
#endif sun2
#if defined(sun4m) && defined(IOMMU)
if (!iom)
bp_map(bp, &DVMA[mmu_ptob(reg)]);
/*
* if it's vme24map, pass DVMA_PEEK flag along
* so that bp_iom_map will load host SRMMU also.
*/
else if (map == vme24map)
bp_iom_map(bp, reg, flags | DVMA_PEEK, map);
else
bp_iom_map(bp, reg, flags, map);
#else
/*
* Map the bp into kernel virtual address in DVMA space.
* We over allocate one slot and mark this mapping
* invalid. This protects against run away transfers
* and is also used as termination condition in mb_mapfree.
* We set the redzone to invalid every time because we're
* paranoid, but we use mmu_setpte instead of hat_pteload
* because we want it to be fast.
*/
bp_map(bp, &DVMA[mmu_ptob(reg)]);
#endif defined(sun4m) && defined(IOMMU)
/* invalid red-zone's mapping */
#if defined(sun3x)
*(u_int *)&pte = MMU_INVALIDPTE;
hat_pteload(&kseg, &DVMA[mmu_ptob(reg + npf)],
(struct page *)NULL, &pte, PTELD_LOCK | PTELD_NOSYNC);
#elif defined(sun4m)
#ifdef IOMMU
/* if iommu is on, only vme24map needs red zone on host */
if (iom==0 || map == vme24map) {
*(u_int *)&pte = MMU_STD_INVALIDPTE;
hat_pteload(&kseg, &DVMA[mmu_ptob(reg + npf)],
(struct page *)NULL, &pte, PTELD_LOCK | PTELD_NOSYNC);
}
#else
*(u_int *)&pte = MMU_STD_INVALIDPTE;
hat_pteload(&kseg, &DVMA[mmu_ptob(reg + npf)],
(struct page *)NULL, &pte, PTELD_LOCK | PTELD_NOSYNC);
#endif IOMMU
#else
mmu_setpte(&DVMA[mmu_ptob(reg + npf)], mmu_pteinvalid);
#endif sun3x
#ifdef sun2
if (ctx != kctx)
mmu_setctx(ctx);
#endif sun2
mbcookie.mbi_mapreg = reg;
mbcookie.mbi_offset = o;
return (*(int *)&mbcookie);
}
/*
* mb_relse now back in sundev/mb.c
*/
/*
* Release resources on Mainbus, and then unblock resource waiters.
*/
void
mb_mapfree(map, amr)
register struct map *map;
int *amr;
{
#if !defined(sun4m) || !defined(IOMMU)
register char *addr;
register int reg;
#endif
register int npf;
#if defined(sun3x) || (defined(sun4m) && !defined(IOMMU))
struct pte *ppte;
#endif
#if defined(sun3x) || defined(sun4m)
#ifdef SUN3X_470
int flush = 0;
#endif SUN3X_470
#else sun3x || sun4m
struct pte pte;
#endif sun3x || sun4m
int mr, s;
#ifdef sun2
struct ctx *ctx;
#endif sun2
#ifdef sun4m
int flush = 0;
#ifdef IOMMU
struct map *ret_map;
union iommu_pte *p_iopte;
#endif
#ifdef IOC
int ioc_mr;
extern void flush_writebuffers();
#endif
#endif
/*
* Carefully see if we should release the space.
*/
s = splvm();
mr = *amr;
if (mr == 0) {
printf("mb_mapfree: MR == 0!!!\n");
(void) splx(s);
return;
}
*amr = 0;
(void) splx(s); /* we're supposed to be safe for awhile */
#if defined(sun3x) || (defined(sun4m) && !defined(IOMMU))
if ((reg = MBI_MR(mr)) < dvmasize) { /* DVMA memory */
/*
* First we have to read the hardware maps to figure
* out how big the transfer really was. This relies
* on having an invalid pte to terminate the mapping.
* We start looking at the 2nd page to avoid always
* looking at the first page.
*/
addr = &DVMA[mmu_ptob(reg + 1)];
/*
* NOTE -- this assumes that the ptes for DVMA space
* are contiguous and ascending. This must remain true!
*/
ppte = (struct pte *)hat_ptefind(&kas, addr);
if (ppte == NULL)
panic("mbrelse no ptes");
/*
* See if the first page of the mapping was I/O cacheable.
*/
#ifdef SUN3X_470
if (cpu == CPU_SUN3X_470) {
if ((ppte - 1)->pte_iocache)
flush = 1;
}
#endif SUN3X_470
#if defined(sun4m) && defined(IOC)
if ((ptetospte(ppte -1))->spte_iocache)
flush = 1;
#endif sun4m && IOC
npf = 2;
for (;;) {
if (!pte_valid(ppte))
break;
npf++;
ppte++;
}
#ifdef SUN3X_470
if (cpu == CPU_SUN3X_470) {
/*
* If the mapping was I/O cacheable, loop
* through the cache flushing all the lines
* for this mapping.
*/
if (flush) {
for (flush = 0; flush < npf; flush++)
ioc_flush(reg + flush);
}
}
#endif SUN3X_470
#if defined(sun4m) && defined(IOC)
if (flush) {
for (flush = 0; flush < npf; flush++)
fast_ioc_flush(reg + flush);
}
flush_writebuffers();
#endif sun4m && IOC
/*
* Now use hat_unload to unload the translations
* without getting the referenced and modified bits.
* Note that even though sun4m needs to have its
* vac tended to, the following should do the
* trick, since it will end up going through the
* hat code.
*/
hat_unload(&kseg, &DVMA[mmu_ptob(reg)],
(u_int)(npf << MMU_PAGESHIFT));
/*
* Put back the registers in the resource map.
* The map code must not be reentered, so we
* do this at high spl.
*/
s = splvm();
rmfree(map, (long)npf, (u_long)reg);
(void) splx(s);
}
#elif defined(sun4m) && defined(IOMMU)
mr= mmu_btop(mr); /* we really work on "pages" */
if ((ret_map= map_addr_to_map(mr, map)) != NULL) { /* DVMA */
#ifdef IOC
/* IOC works with VME devices only, no S-bus devices */
if (ret_map == vme32map || ret_map == vme24map) {
/* see if IOC is on. */
if (ioc_read_tag(mr) & IOC_LINE_ENABLE) {
flush= 1;
}
/* see if there is padding at beginning */
ioc_mr= mr= ioc_adj_start(mr);
}
#endif IOC
if ((p_iopte= iom_ptefind(mr, ret_map)) == NULL)
panic("mb_mapfree: no iopte");
/* count how many pages are there */
for (npf= 0; ; p_iopte++, npf++) {
if (p_iopte-> iopte.valid == 0)
break;
#ifdef IOC
/* flush every other page, we know ioc is 8K */
if (flush && ((npf & 1)==0)) {
fast_ioc_flush(ioc_mr);
ioc_setup(ioc_mr, IOC_LINE_INVALID);
ioc_mr += IOC_PAGES_PER_LINE;
}
#endif IOC
/* sync mod/ref bits */
/* NOTE: I do not think we really need
* this iom_pagesync() call.
*
* ****************************
* iom_pagesync(p_iopte);
* ****************************
*/
/* unload IOMMU */
iom_pteunload(p_iopte);
}
flush_writebuffers();
if (ret_map == vme24map) {
/*
* NOTE: mr and npf here INCLUDES paddings.
* Although we never loaded padding
* pages into host SRMMU, but we ask
* hat_unload to unload them anyway,
* since it can take this kind of
* abuse. Otherwise, we will have
* to figure out the exact pages
* to be unloaded (excluding paddings).
*/
hat_unload(&kseg, &DVMA[mmu_ptob(mr)],
(u_int)mmu_ptob(npf));
}
s = splvm();
/* add one for red zone */
rmfree(ret_map, (long) (npf+1), (u_long)mr);
(void) splx(s);
}
#else defined(sun4m)
if ((reg = MBI_MR(mr)) < dvmasize) { /* DVMA memory */
#ifdef sun2
ctx = mmu_getctx();
if (ctx != kctx)
mmu_setctx(kctx);
#endif sun2
#ifdef sun4c
/*
* First we have to read the hardware maps to figure
* out how big the transfer really was. This relies
* on having an invalid pte to terminate the mapping.
* We start looking at the first page since we are
* syncing the cache as we go. We only sync the
* cache for dirty pages, since output doesn't
* make the cache stale. This assumes that the
* I/O is going through the MMU and thus setting
* the dirty bit when appropriate.
*
* NOTE: this method of syncing the cache only
* works for write-through caches, where the flush
* is always an invalidate. A write-back cache
* would not work this way.
*
* Ah, but as this is an allocation in DVMA, then
* we didn't use segkmem and the hat layer to load
* things up, as cache consistency is not germane
* to sun4c where I/O doesn't go through the cache
* anyway. We still have to flush any I/U cache
* references to this physical page, but we can
* just nuke the pte mapping as we go..
*/
addr = &DVMA[mmu_ptob(reg)];
npf = 0;
for (;;) {
mmu_getpte((addr_t)addr, &pte);
if (!pte_valid(&pte))
break;
#ifdef VAC
if (vac && pte.pg_m)
hat_vacsync(MAKE_PFNUM(&pte));
#endif VAC
mmu_setpte((addr_t)addr, mmu_pteinvalid);
addr += MMU_PAGESIZE;
npf++;
}
#else sun4c
#ifdef VAC
#ifdef sun4m
if (cache) {
#else
if (vac) {
#endif sun4m
/*
* First we have to read the hardware maps to figure
* out how big the transfer really was. This relies
* on having an invalid pte to terminate the mapping.
* If there is an i/o cache we have to check the
* first page to see if it needs to be flushed, so
* to make this code cleaner we just always start at
* the first page even though for non ioc machines
* we could start at the second.
*/
addr = &DVMA[mmu_ptob(reg)];
npf = 0;
for (;;) {
mmu_getpte((addr_t)addr, &pte);
if (!pte_valid(&pte))
break;
#ifdef IOC
if (pte.pg_ioc)
ioc_flush(reg + npf);
#endif IOC
addr += MMU_PAGESIZE;
npf++;
}
/*
* Now use hat_unload to unload the translations
* without getting the referenced and modified bits.
*/
hat_unload(&kseg, &DVMA[mmu_ptob(reg)],
(u_int)mmu_ptob(npf));
} else
#endif VAC
{
/*
* No vac, can just use mmu operations to unload
* the DVMA mappings.
*/
addr = &DVMA[mmu_ptob(reg)];
npf = 0;
for (;;) {
mmu_getpte((addr_t)addr, &pte);
if (!pte_valid(&pte))
break;
#ifdef IOC
if (pte.pg_ioc)
ioc_flush(reg + npf);
#endif IOC
mmu_setpte((addr_t)addr, mmu_pteinvalid);
addr += MMU_PAGESIZE;
npf++;
}
}
#endif sun4c
#ifdef sun2
if (ctx != kctx)
mmu_setctx(ctx);
#endif sun2
/*
* Put back the registers in the resource map.
* The map code must not be reentered, so we
* do this at high spl.
*/
s = splvm();
rmfree(map, (long)(npf + 1), (u_long)reg);
(void) splx(s);
}
#endif sun3x
/*
* Try to map waiting devices, using the queued function ptr.
* If a request can't be mapped, we just return, as mb_mapalloc
* has already requeued the request.
*/
s = splvm();
if (mbwaitq)
mbq_retrieve(spltoipl(s));
(void) splx(s);
}
/*
* Store a queue element, returning if the funcp is already queued.
* Always called at splvm().
*/
static void
mbq_store(funcp, arg, pri)
func_t funcp;
caddr_t arg;
int pri;
{
register struct mbq_elem *lq = NULL;
register struct mbq_elem *q = mbwaitq;
/*
* Search the queue to see whether funcp is still queued
*/
while (q != NULL) {
if (q->mbq_funcp == funcp) {
mbs.m_queued++;
return;
}
lq = q;
q = q->mbq_next;
}
if ((q = mbqfree) == NULL) {
int i;
/*
* If mbwaitq is NULL when mbqfree is NULL, then
* this is the first time thru, else we just got
* too many callers. Die.
*/
if (mbwaitq) {
panic("mbq_store: too many callers");
/*NOTREACHED*/
}
/*
* ...else initialize the free queue
*/
for (i = 0; i < MAXMBQLEN-1; i++)
mbq[i].mbq_next = &mbq[i+1];
q = mbqfree = mbq;
}
mbqfree = q->mbq_next;
q->mbq_funcp = funcp;
q->mbq_arg = arg;
if ((q->mbq_pri = pri) == 0) {
printf("Warning: MB callback stored at priority 0!\n");
}
if (lq == NULL) {
q->mbq_next = mbwaitq;
mbwaitq = q;
} else {
q->mbq_next = (struct mbq_elem *) NULL;
lq->mbq_next = q;
}
mbs.m_runout++;
mbqlen++;
mbs.m_hiqlen = MAX(mbqlen, mbs.m_hiqlen);
}
/*
* Retrieve queue elements from the wait queue.
* Always called at splvm(). Honor protocol of
* first calling directly only waiters who are
* waiting at pri or greater, using softcall
* to call everybody else (if resources are
* still left).
*/
static void
mbq_retrieve(pri)
int pri;
{
register struct mbq_elem *q, *pq;
int stat;
if ((q = mbwaitq) == (struct mbq_elem *) NULL)
return;
pq = (struct mbq_elem *) NULL;
stat = 0;
do {
if (q->mbq_pri >= pri) {
register func_t func;
register caddr_t arg;
/*
* save a copy of the function/argument pair
*/
func = q->mbq_funcp;
arg = q->mbq_arg;
/*
* Snip the queue element out of the chain
* and put it back on the free list before
* doing the callback.
*/
if (pq) {
pq->mbq_next = q->mbq_next;
q->mbq_next = mbqfree;
mbqfree = q;
q = pq->mbq_next;
} else {
mbwaitq = q->mbq_next;
q->mbq_next = mbqfree;
mbqfree = q;
q = mbwaitq;
}
mbqlen -= 1;
/*
* and call the function specified
*/
stat = (*(func)) (arg);
/*
* If we ran out again, all bets are off
*/
if (stat == DVMA_RUNOUT)
break;
} else {
pq = q;
q = q->mbq_next;
}
} while (q != (struct mbq_elem *) NULL);
if (stat != DVMA_RUNOUT && mbwaitq != (struct mbq_elem *) NULL) {
softcall(mbq_softcall, (caddr_t) 0);
}
}
/*ARGSUSED*/
static int
mbq_softcall(arg)
caddr_t arg;
{
register s = splvm();
mbs.m_softcall++;
mbq_retrieve(0);
(void) splx(s);
}
/*
* Put swab here, as mb_machdep.c is compiled common to
* both sun4c and sun{2, 3, 4} builds.
*/
/*
* Swap bytes in 16-bit [half-]words
* for going between the 11 and the interdata
*/
void
swab(pf, pt, n)
register caddr_t pf, pt;
register int n;
{
register char temp;
n = (n+1)>>1;
while (--n >= 0) {
temp = *pf++;
*pt++ = *pf++;
*pt++ = temp;
}
}
/*
* Non buffer setup interface... set up a buffer and call mb_mapalloc.
* If DVMA runs out. waitfp is queued and invoked later when space becomes
* available.
*/
int
mb_nbmapalloc(map, addr, bcnt, flags, waitfp, arg)
struct map *map;
caddr_t addr;
int bcnt, flags;
func_t waitfp;
caddr_t arg;
{
struct buf mbbuf;
bzero((caddr_t)&mbbuf, sizeof (mbbuf));
mbbuf.b_un.b_addr = addr;
mbbuf.b_flags = B_BUSY;
mbbuf.b_bcount = bcnt;
return (mb_mapalloc(map, &mbbuf, flags, waitfp, arg));
}