1853 lines
46 KiB
C
Executable File
1853 lines
46 KiB
C
Executable File
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
|
|
/* All Rights Reserved */
|
|
|
|
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
|
|
/* The copyright notice above does not evidence any */
|
|
/* actual or intended publication of such source code. */
|
|
|
|
/*
|
|
* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
* PROPRIETARY NOTICE (Combined)
|
|
*
|
|
* This source code is unpublished proprietary information
|
|
* constituting, or derived under license from AT&T's UNIX(r) System V.
|
|
* In addition, portions of such source code were derived from Berkeley
|
|
* 4.3 BSD under license from the Regents of the University of
|
|
* California.
|
|
*
|
|
*
|
|
*
|
|
* Copyright Notice
|
|
*
|
|
* Notice of copyright on this source code product does not indicate
|
|
* publication.
|
|
*
|
|
* (c) 1986, 1987, 1988, 1989, 1990, 1991, 1993 Sun Microsystems, Inc
|
|
* (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T.
|
|
* All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#ident "@(#)seg_mapdev.c 1.23 95/08/07 SMI"
|
|
|
|
/*
|
|
* VM - mapdev segment of a mapped device.
|
|
*
|
|
* This segment driver is used when mapping character special devices.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/kmem.h>
|
|
#include <sys/cmn_err.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/debug.h>
|
|
#include <sys/seg_mapdev.h>
|
|
#include <sys/thread.h>
|
|
|
|
#include <vm/page.h>
|
|
#include <vm/hat.h>
|
|
#include <vm/as.h>
|
|
#include <vm/seg.h>
|
|
#include <vm/devpage.h>
|
|
#include <vm/vpage.h>
|
|
#include <sys/fs/snode.h>
|
|
|
|
#include <sys/ddi.h>
|
|
#include <sys/esunddi.h>
|
|
|
|
/*
|
|
* Private seg op routines.
|
|
*/
|
|
|
|
static int segmapdev_dup(struct seg *, struct seg *);
|
|
static int segmapdev_unmap(struct seg *, caddr_t, u_int);
|
|
static void segmapdev_free(struct seg *);
|
|
static faultcode_t segmapdev_fault(struct hat *, struct seg *, caddr_t, u_int,
|
|
enum fault_type, enum seg_rw);
|
|
static faultcode_t segmapdev_faulta(struct seg *, caddr_t);
|
|
static int segmapdev_setprot(struct seg *, caddr_t, u_int, u_int);
|
|
static int segmapdev_checkprot(struct seg *, caddr_t, u_int, u_int);
|
|
static void segmapdev_badop(void);
|
|
static int segmapdev_sync(struct seg *, caddr_t, u_int, int, u_int);
|
|
static int segmapdev_incore(struct seg *, caddr_t, u_int, char *);
|
|
static int segmapdev_lockop(struct seg *, caddr_t, u_int, int, int,
|
|
ulong *, size_t);
|
|
static int segmapdev_getprot(struct seg *, caddr_t, u_int, u_int *);
|
|
static off_t segmapdev_getoffset(struct seg *, caddr_t);
|
|
static int segmapdev_gettype(struct seg *, caddr_t);
|
|
static int segmapdev_getvp(struct seg *, caddr_t, struct vnode **);
|
|
static int segmapdev_advise(struct seg *, caddr_t, u_int, int);
|
|
static void segmapdev_dump(struct seg *);
|
|
|
|
/*
|
|
* XXX this struct is used by rootnex_map_fault to identify
|
|
* the segment it has been passed. So if you make it
|
|
* "static" you'll need to fix rootnex_map_fault.
|
|
*/
|
|
struct seg_ops segmapdev_ops = {
|
|
segmapdev_dup,
|
|
segmapdev_unmap,
|
|
segmapdev_free,
|
|
segmapdev_fault,
|
|
segmapdev_faulta,
|
|
segmapdev_setprot,
|
|
segmapdev_checkprot,
|
|
(int (*)())segmapdev_badop, /* kluster */
|
|
(u_int (*)(struct seg *))NULL, /* swapout */
|
|
segmapdev_sync, /* sync */
|
|
segmapdev_incore,
|
|
segmapdev_lockop, /* lockop */
|
|
segmapdev_getprot,
|
|
segmapdev_getoffset,
|
|
segmapdev_gettype,
|
|
segmapdev_getvp,
|
|
segmapdev_advise,
|
|
segmapdev_dump,
|
|
};
|
|
|
|
/*
|
|
* Max number of ticks that a thread can block other threads from taking the
|
|
* device context on MP machines.
|
|
*/
|
|
#define LEAVE_ONCPU 1
|
|
static int segmapdev_leaveoncpu = LEAVE_ONCPU;
|
|
/*
|
|
* List of device context private data. One per device
|
|
*/
|
|
static struct segmapdev_ctx *devctx_list = NULL;
|
|
/*
|
|
* Protects devctx_list
|
|
*/
|
|
static kmutex_t devctx_lock;
|
|
|
|
#define vpgtob(n) ((n) * sizeof (struct vpage)) /* For brevity */
|
|
|
|
#define VTOCVP(vp) (VTOS(vp)->s_commonvp) /* we "know" it's an snode */
|
|
|
|
/*
|
|
* Private support routines
|
|
*/
|
|
|
|
static struct segmapdev_data *sdp_alloc(void);
|
|
|
|
static void segmapdev_softunlock(struct hat *, struct seg *, caddr_t,
|
|
u_int, enum seg_rw, cred_t *);
|
|
|
|
static int segmapdev_faultpage(struct hat *, struct seg *, caddr_t,
|
|
struct vpage *, u_int *, enum fault_type, enum seg_rw, cred_t *);
|
|
|
|
static int segmapdev_faultpages(struct hat *, struct seg *, caddr_t,
|
|
u_int, enum fault_type, enum seg_rw);
|
|
|
|
/*
|
|
* The following routines are used to manage thread context callbacks and
|
|
* are used to prevent thrashing
|
|
*/
|
|
static struct segmapdev_ctx *segmapdev_ctxfind(dev_t dev, u_int id);
|
|
extern void map_addr(caddr_t *, u_int, off_t, int);
|
|
|
|
/*
|
|
* Find a devctx struct in the list of devctx structs. If there is no devctx
|
|
* struct for this device yet, return NULL
|
|
*/
|
|
static struct segmapdev_ctx *
|
|
segmapdev_ctxfind(dev_t dev, u_int id)
|
|
{
|
|
struct segmapdev_ctx *devctx;
|
|
|
|
ASSERT(mutex_owned(&devctx_lock));
|
|
|
|
for (devctx = devctx_list; devctx != NULL; devctx = devctx->next)
|
|
if ((devctx->dev == dev) && (devctx->id == id))
|
|
return (devctx);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Initialize the thread callbacks and thread private data.
|
|
*/
|
|
static struct segmapdev_ctx *
|
|
segmapdev_ctxinit(dev_t dev, u_int id)
|
|
{
|
|
struct segmapdev_ctx *devctx;
|
|
|
|
mutex_enter(&devctx_lock);
|
|
|
|
/*
|
|
* Get the devctx struct for this device. If one has not been
|
|
* created yet, create a new one.
|
|
*/
|
|
if ((devctx = segmapdev_ctxfind(dev, id)) == NULL) {
|
|
devctx = kmem_alloc(sizeof (struct segmapdev_ctx), KM_SLEEP);
|
|
devctx->dev = dev;
|
|
devctx->id = id;
|
|
devctx->oncpu = 0;
|
|
devctx->refcnt = 0;
|
|
devctx->timeout = 0;
|
|
devctx->next = devctx_list;
|
|
devctx_list = devctx;
|
|
mutex_init(&devctx->lock, "device context lock",
|
|
MUTEX_DEFAULT, NULL);
|
|
cv_init(&devctx->cv, "device context cv",
|
|
CV_DEFAULT, NULL);
|
|
}
|
|
|
|
devctx->refcnt++;
|
|
|
|
mutex_exit(&devctx_lock);
|
|
return (devctx);
|
|
|
|
}
|
|
|
|
/*
|
|
* Timeout callback called if a CPU has not given up the device context
|
|
* within sdp->timeout_length ticks
|
|
*/
|
|
static void
|
|
segmapdev_ctxto(caddr_t data)
|
|
{
|
|
struct segmapdev_ctx *devctx = (struct segmapdev_ctx *)data;
|
|
|
|
mutex_enter(&devctx->lock);
|
|
/*
|
|
* Set oncpu = 0 so the next mapping trying to get the device context
|
|
* can.
|
|
*/
|
|
devctx->oncpu = 0;
|
|
devctx->timeout = 0;
|
|
cv_signal(&devctx->cv);
|
|
mutex_exit(&devctx->lock);
|
|
}
|
|
|
|
/*
|
|
* Create a device segment.
|
|
*/
|
|
static int
|
|
segmapdev_create(struct seg *seg, void *argsp)
|
|
{
|
|
register struct segmapdev_data *sdp;
|
|
register struct segmapdev_crargs *a = (struct segmapdev_crargs *)argsp;
|
|
register int error;
|
|
|
|
/*
|
|
* Since the address space is "write" locked, we
|
|
* don't need the segment lock to protect "segmapdev" data.
|
|
*/
|
|
ASSERT(seg->s_as && AS_WRITE_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
/*
|
|
* The following call to hat_map presumes that translation resources
|
|
* are set up by the system MMU. This may cause problems when the
|
|
* resources are allocated/managed by the device's MMU or an MMU
|
|
* other than the system MMU. For now hat_map is no-op and not
|
|
* implemented by the Sun MMU, SRMMU and X86 MMU drivers and should
|
|
* not pose any problems.
|
|
*/
|
|
error = hat_map(seg->s_as->a_hat, seg->s_as, seg->s_base,
|
|
seg->s_size, HAT_MAP);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
/* TODO: try concatenation */
|
|
|
|
if ((sdp = sdp_alloc()) == NULL)
|
|
return (ENOMEM);
|
|
|
|
sdp->mapfunc = a->mapfunc;
|
|
sdp->offset = a->offset;
|
|
sdp->pageprot = 0;
|
|
sdp->prot = a->prot;
|
|
sdp->maxprot = a->maxprot;
|
|
sdp->flags = a->flags;
|
|
sdp->vpage = NULL;
|
|
sdp->pagehat_flags = 0;
|
|
sdp->hat_flags = 0;
|
|
sdp->vpage_hat_flags = NULL;
|
|
sdp->vpage_inter = NULL;
|
|
sdp->hat = NULL;
|
|
sdp->dev = a->dev;
|
|
|
|
/*
|
|
* Set to intercept by default
|
|
*/
|
|
sdp->flags = SEGMAPDEV_INTER;
|
|
|
|
/*
|
|
* Set default number of clock ticks to keep ctx
|
|
*/
|
|
sdp->timeout_length = segmapdev_leaveoncpu;
|
|
|
|
/*
|
|
* Save away driver private data and callbacks
|
|
*/
|
|
sdp->m_ops.mapdev_rev = a->m_ops->mapdev_rev;
|
|
sdp->m_ops.mapdev_access = a->m_ops->mapdev_access;
|
|
sdp->m_ops.mapdev_free = a->m_ops->mapdev_free;
|
|
sdp->m_ops.mapdev_dup = a->m_ops->mapdev_dup;
|
|
sdp->private_data = a->private_data;
|
|
|
|
/*
|
|
* Return the ddi_mapdev_handle.
|
|
*/
|
|
*a->handle = seg;
|
|
|
|
/*
|
|
* Hold common vnode -- segmapdev only deals with
|
|
* character (VCHR) devices, and uses the common
|
|
* vp to hang devpages on.
|
|
*/
|
|
sdp->vp = specfind(a->dev, VCHR);
|
|
ASSERT(sdp->vp != NULL);
|
|
|
|
/*
|
|
* Initialize the device context data
|
|
*/
|
|
sdp->devctx = segmapdev_ctxinit(sdp->dev,
|
|
(u_int)sdp->m_ops.mapdev_access);
|
|
|
|
seg->s_ops = &segmapdev_ops;
|
|
seg->s_data = sdp;
|
|
|
|
/*
|
|
* Inform the vnode of the new mapping.
|
|
*/
|
|
error = VOP_ADDMAP(VTOCVP(sdp->vp), (offset_t)sdp->offset,
|
|
seg->s_as, seg->s_base, seg->s_size,
|
|
sdp->prot, sdp->maxprot, MAP_SHARED, CRED());
|
|
if (error != 0)
|
|
hat_unmap(seg->s_as, seg->s_base, seg->s_size, HAT_UNMAP);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static struct segmapdev_data *
|
|
sdp_alloc(void)
|
|
{
|
|
register struct segmapdev_data *sdp;
|
|
|
|
sdp = (struct segmapdev_data *)
|
|
kmem_zalloc(sizeof (struct segmapdev_data), KM_SLEEP);
|
|
mutex_init(&sdp->lock, "mapdev_sdp.lock", MUTEX_DEFAULT, NULL);
|
|
cv_init(&sdp->wait, "mapdev_spd.wait", CV_DEFAULT, NULL);
|
|
return (sdp);
|
|
}
|
|
|
|
/*
|
|
* Duplicate seg and return new segment in newseg.
|
|
*/
|
|
static int
|
|
segmapdev_dup(struct seg *seg, struct seg *newseg)
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register struct segmapdev_data *newsdp;
|
|
|
|
ASSERT(seg->s_as && AS_WRITE_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
if ((newsdp = sdp_alloc()) == NULL)
|
|
return (ENOMEM);
|
|
|
|
newseg->s_ops = seg->s_ops;
|
|
newseg->s_data = (void *)newsdp;
|
|
|
|
mutex_enter(&sdp->lock);
|
|
VN_HOLD(sdp->vp);
|
|
newsdp->mapfunc = sdp->mapfunc;
|
|
newsdp->offset = sdp->offset;
|
|
newsdp->vp = sdp->vp;
|
|
newsdp->pageprot = sdp->pageprot;
|
|
newsdp->prot = sdp->prot;
|
|
newsdp->maxprot = sdp->maxprot;
|
|
newsdp->flags = sdp->flags;
|
|
newsdp->pagehat_flags = sdp->pagehat_flags;
|
|
newsdp->hat_flags = sdp->hat_flags;
|
|
newsdp->hat = NULL;
|
|
newsdp->dev = sdp->dev;
|
|
|
|
/*
|
|
* Set timeout_length
|
|
*/
|
|
newsdp->timeout_length = sdp->timeout_length;
|
|
|
|
/*
|
|
* Initialize the device context data
|
|
*/
|
|
newsdp->devctx = segmapdev_ctxinit(newsdp->dev,
|
|
(u_int)newsdp->m_ops.mapdev_access);
|
|
|
|
/*
|
|
* Save away driver private data and callbacks
|
|
*/
|
|
newsdp->private_data = sdp->private_data;
|
|
newsdp->m_ops.mapdev_rev = sdp->m_ops.mapdev_rev;
|
|
newsdp->m_ops.mapdev_access = sdp->m_ops.mapdev_access;
|
|
newsdp->m_ops.mapdev_free = sdp->m_ops.mapdev_free;
|
|
newsdp->m_ops.mapdev_dup = sdp->m_ops.mapdev_dup;
|
|
|
|
/*
|
|
* Initialize per page interception data if the segment we are
|
|
* dup'ing has per page interception information.
|
|
*/
|
|
if (sdp->vpage_inter != NULL) {
|
|
newsdp->vpage_inter = kmem_alloc(seg_pages(newseg), KM_SLEEP);
|
|
bcopy((caddr_t)sdp->vpage_inter, (caddr_t)newsdp->vpage_inter,
|
|
seg_pages(newseg));
|
|
} else
|
|
newsdp->vpage_inter = NULL;
|
|
|
|
/*
|
|
* Initialize per page hat-flag data if the segment we are
|
|
* dup'ing has per page hat-flag information.
|
|
*/
|
|
if (sdp->vpage_hat_flags != NULL) {
|
|
newsdp->vpage_hat_flags =
|
|
kmem_alloc(seg_pages(newseg), KM_SLEEP);
|
|
bcopy((caddr_t)sdp->vpage_hat_flags,
|
|
(caddr_t)newsdp->vpage_hat_flags, seg_pages(newseg));
|
|
} else
|
|
newsdp->vpage_hat_flags = NULL;
|
|
|
|
if (sdp->vpage != NULL) {
|
|
register u_int nbytes = vpgtob(seg_pages(seg));
|
|
/*
|
|
* Release the segment lock while allocating
|
|
* the vpage structure for the new segment
|
|
* since nobody can create it and our segment
|
|
* cannot be destroyed as the address space
|
|
* has a "read" lock.
|
|
*/
|
|
mutex_exit(&sdp->lock);
|
|
newsdp->vpage = (struct vpage *)kmem_alloc(nbytes, KM_SLEEP);
|
|
mutex_enter(&sdp->lock);
|
|
bcopy((caddr_t)sdp->vpage, (caddr_t)newsdp->vpage, nbytes);
|
|
} else
|
|
newsdp->vpage = NULL;
|
|
|
|
mutex_exit(&sdp->lock);
|
|
|
|
if (sdp->m_ops.mapdev_dup) {
|
|
int ret;
|
|
|
|
/*
|
|
* Call the dup callback so that the driver can duplicate
|
|
* its private data.
|
|
*/
|
|
ret = (*sdp->m_ops.mapdev_dup)
|
|
(seg, sdp->private_data, newseg, &newsdp->private_data);
|
|
|
|
if (ret != 0) {
|
|
/*
|
|
* We want to free up this segment as the driver has
|
|
* indicated that we can't dup it. But we don't want
|
|
* to call the drivers callback func as the driver
|
|
* does not think this segment exists.
|
|
*
|
|
* The caller of segmapdev_dup will call seg_free on
|
|
* newseg as it was the caller that allocated the
|
|
* segment.
|
|
*/
|
|
newsdp->m_ops.mapdev_free = NULL;
|
|
return (ret);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Inform the common vnode of the new mapping.
|
|
*/
|
|
return (VOP_ADDMAP(VTOCVP(newsdp->vp),
|
|
(offset_t)newsdp->offset, newseg->s_as,
|
|
newseg->s_base, newseg->s_size, newsdp->prot,
|
|
newsdp->maxprot, MAP_SHARED, CRED()));
|
|
}
|
|
|
|
/*
|
|
* unmap a mapdev'd region.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static int
|
|
segmapdev_unmap(register struct seg *seg, register caddr_t addr, u_int len)
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
|
|
/*
|
|
* Since the address space is "write" locked, we
|
|
* don't need the segment lock to protect "segmapdev" data.
|
|
*/
|
|
ASSERT(seg->s_as && AS_WRITE_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
/*
|
|
* For mapdev, we require that the entire segment be unmapped.
|
|
* We do not support partial unmapping of segments.
|
|
*/
|
|
if (addr != seg->s_base || len != seg->s_size)
|
|
return (EINVAL);
|
|
|
|
/*
|
|
* Unload any hardware translations in the range
|
|
*/
|
|
hat_unmap(seg->s_as, addr, len, HAT_UNMAP);
|
|
|
|
/*
|
|
* Inform the vnode of the unmapping.
|
|
*/
|
|
ASSERT(sdp->vp != NULL);
|
|
if (VOP_DELMAP(VTOCVP(sdp->vp),
|
|
(offset_t)sdp->offset + (addr - seg->s_base),
|
|
seg->s_as, addr, len, sdp->prot, sdp->maxprot,
|
|
MAP_SHARED, CRED()) != 0)
|
|
cmn_err(CE_WARN, "segmapdev_unmap VOP_DELMAP failure");
|
|
|
|
/*
|
|
* Segmapdev_free will call the free callback into the driver.
|
|
* We don't have to do anything more here.
|
|
*/
|
|
seg_free(seg);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Free a segment.
|
|
*/
|
|
static void
|
|
segmapdev_free(struct seg *seg)
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register u_int nbytes = vpgtob(seg_pages(seg));
|
|
struct segmapdev_ctx *devctx;
|
|
struct segmapdev_ctx *parent;
|
|
struct segmapdev_ctx *tmp;
|
|
|
|
/*
|
|
* Since the address space is "write" locked, we
|
|
* don't need the segment lock to protect "segmapdev" data.
|
|
*/
|
|
ASSERT(seg->s_as && AS_WRITE_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
/*
|
|
* Call the free callback so that the driver can free up its
|
|
* private data.
|
|
*/
|
|
if (sdp->m_ops.mapdev_free)
|
|
(*sdp->m_ops.mapdev_free)(seg, sdp->private_data);
|
|
|
|
/*
|
|
* If we have per page interception information free it up.
|
|
*/
|
|
if (sdp->vpage_inter)
|
|
kmem_free(sdp->vpage_inter, seg_pages(seg));
|
|
|
|
/*
|
|
* If we have per page hat-flags information free it up.
|
|
*/
|
|
if (sdp->vpage_hat_flags)
|
|
kmem_free(sdp->vpage_hat_flags, seg_pages(seg));
|
|
|
|
VN_RELE(sdp->vp);
|
|
if (sdp->vpage != NULL)
|
|
kmem_free(sdp->vpage, nbytes);
|
|
|
|
devctx = sdp->devctx;
|
|
|
|
/*
|
|
* Untimeout any threads using this mapping as they are about
|
|
* to go away.
|
|
*/
|
|
if (devctx->timeout != 0)
|
|
(void) untimeout(devctx->timeout);
|
|
|
|
mutex_enter(&devctx_lock);
|
|
mutex_enter(&devctx->lock);
|
|
|
|
/*
|
|
* If a mapping is waiting for this device context, set it free.
|
|
*/
|
|
devctx->oncpu = 0;
|
|
cv_signal(&devctx->cv);
|
|
|
|
/*
|
|
* Decrement reference count
|
|
*/
|
|
devctx->refcnt--;
|
|
|
|
/*
|
|
* If no one is using the device, free up the devctx data.
|
|
*/
|
|
if (devctx->refcnt <= 0) {
|
|
if (devctx_list == devctx)
|
|
devctx_list = devctx->next;
|
|
else {
|
|
parent = devctx_list;
|
|
for (tmp = devctx_list->next; tmp != NULL;
|
|
tmp = tmp->next) {
|
|
if (tmp == devctx) {
|
|
parent->next = tmp->next;
|
|
break;
|
|
}
|
|
parent = tmp;
|
|
}
|
|
}
|
|
mutex_exit(&devctx->lock);
|
|
mutex_destroy(&devctx->lock);
|
|
cv_destroy(&devctx->cv);
|
|
kmem_free(devctx, sizeof (struct segmapdev_ctx));
|
|
} else
|
|
mutex_exit(&devctx->lock);
|
|
mutex_exit(&devctx_lock);
|
|
mutex_destroy(&sdp->lock);
|
|
cv_destroy(&sdp->wait);
|
|
kmem_free(sdp, sizeof (*sdp));
|
|
}
|
|
|
|
/*
|
|
* Do a F_SOFTUNLOCK call over the range requested.
|
|
* The range must have already been F_SOFTLOCK'ed.
|
|
* The segment lock should be held.
|
|
*/
|
|
static void
|
|
segmapdev_softunlock(
|
|
struct hat *hat, /* the hat */
|
|
struct seg *seg, /* seg_dev of interest */
|
|
register caddr_t addr, /* base address of range */
|
|
u_int len, /* number of bytes */
|
|
enum seg_rw rw, /* type of access at fault */
|
|
cred_t *cr) /* credentials */
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register u_int offset;
|
|
register struct devpage *dp;
|
|
register caddr_t a;
|
|
struct page *pl[2];
|
|
|
|
offset = sdp->offset + (addr - seg->s_base);
|
|
for (a = addr; a < addr + len; a += PAGESIZE) {
|
|
(void) VOP_GETPAGE(VTOCVP(sdp->vp), (offset_t)offset, PAGESIZE,
|
|
(u_int *)NULL, pl, sizeof (pl) / sizeof (*pl),
|
|
seg, a, rw, cr);
|
|
dp = (devpage_t *)pl[0];
|
|
|
|
if (dp != NULL) {
|
|
if (rw == S_WRITE)
|
|
hat_setmod((struct page *)dp);
|
|
if (rw != S_OTHER)
|
|
hat_setref((struct page *)dp);
|
|
}
|
|
hat_unlock(hat, seg->s_as, a, PAGESIZE);
|
|
|
|
offset += PAGESIZE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handle a single devpage.
|
|
* Done in a separate routine so we can handle errors more easily.
|
|
* This routine is called only from segmapdev_fault()
|
|
* when looping over the range of addresses requested. The
|
|
* segment lock should be held.
|
|
*
|
|
* The basic algorithm here is:
|
|
* Find pfn from the driver's mmap function
|
|
* Load up the translation to the devpage
|
|
* return
|
|
*/
|
|
static int
|
|
segmapdev_faultpage(
|
|
struct hat *hat, /* the hat */
|
|
struct seg *seg, /* seg_dev of interest */
|
|
caddr_t addr, /* address in as */
|
|
struct vpage *vpage, /* pointer to vpage for seg, addr */
|
|
u_int *vpage_hat_flags, /* pointer to vpage_hat_flags for seg, addr */
|
|
enum fault_type type, /* type of fault */
|
|
enum seg_rw rw, /* type of access at fault */
|
|
cred_t *cr) /* credentials */
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register struct devpage *dp;
|
|
register u_int prot;
|
|
register u_int pfnum;
|
|
register u_int offset;
|
|
register int err;
|
|
u_int hat_flags;
|
|
struct page *pl[2];
|
|
dev_info_t *dip;
|
|
extern int pf_is_memory();
|
|
|
|
/*
|
|
* Initialize protection value for this page.
|
|
* If we have per page protection values check it now.
|
|
*/
|
|
if (sdp->pageprot) {
|
|
u_int protchk;
|
|
|
|
switch (rw) {
|
|
case S_READ:
|
|
protchk = PROT_READ;
|
|
break;
|
|
case S_WRITE:
|
|
protchk = PROT_WRITE;
|
|
break;
|
|
case S_EXEC:
|
|
protchk = PROT_EXEC;
|
|
break;
|
|
case S_OTHER:
|
|
default:
|
|
protchk = PROT_READ | PROT_WRITE | PROT_EXEC;
|
|
break;
|
|
}
|
|
|
|
prot = vpage->vp_prot;
|
|
if ((prot & protchk) == 0)
|
|
return (FC_PROT); /* illegal access type */
|
|
} else {
|
|
prot = sdp->prot;
|
|
}
|
|
|
|
offset = sdp->offset + (addr - seg->s_base);
|
|
|
|
pfnum = cdev_mmap(sdp->mapfunc, sdp->vp->v_rdev, offset, prot);
|
|
if (pfnum == (u_int)-1)
|
|
return (FC_MAKE_ERR(EFAULT));
|
|
|
|
hat_flags = *vpage_hat_flags;
|
|
hat_flags |= (type == F_SOFTLOCK) ? HAT_LOCK : HAT_LOAD;
|
|
if (pf_is_memory(pfnum)) {
|
|
hat_devload(hat, seg->s_as, addr, NULL, pfnum,
|
|
prot, hat_flags | HAT_NOCONSIST);
|
|
return (0);
|
|
}
|
|
|
|
err = VOP_GETPAGE(VTOCVP(sdp->vp), (offset_t)offset, PAGESIZE,
|
|
(u_int *)NULL, pl, sizeof (pl) / sizeof (*pl), seg, addr, rw, cr);
|
|
if (err)
|
|
return (FC_MAKE_ERR(err));
|
|
|
|
dp = (devpage_t *)pl[0];
|
|
if (dp != NULL)
|
|
hat_setref((struct page *)dp);
|
|
|
|
dip = VTOS(VTOCVP(sdp->vp))->s_dip;
|
|
ASSERT(dip);
|
|
|
|
/*
|
|
* For performance reasons, call had_devload directly instead of
|
|
* calling up the tree with ddi_map_fault()
|
|
*/
|
|
hat_devload(hat, seg->s_as, addr, dp, pfnum, prot, hat_flags);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* This routine is called via a machine specific fault handling routine.
|
|
* It is also called by software routines wishing to lock or unlock
|
|
* a range of addresses.
|
|
*/
|
|
static faultcode_t
|
|
segmapdev_fault(
|
|
struct hat *hat, /* the hat */
|
|
struct seg *seg, /* the seg_dev of interest */
|
|
register caddr_t addr, /* the address of the fault */
|
|
u_int len, /* the length of the range */
|
|
enum fault_type type, /* type of fault */
|
|
register enum seg_rw rw) /* type of access at fault */
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register caddr_t a;
|
|
int page;
|
|
char call_access = 0;
|
|
int ret;
|
|
int do_timeout = 0;
|
|
struct segmapdev_ctx *devctx;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
if (type == F_PROT) {
|
|
/*
|
|
* Since the seg_mapdev driver does not implement copy-on-write,
|
|
* this means that a valid translation is already loaded,
|
|
* but we got an fault trying to access the device.
|
|
* Return an error here to prevent going in an endless
|
|
* loop reloading the same translation...
|
|
*/
|
|
return (FC_PROT);
|
|
}
|
|
|
|
/*
|
|
* We need to call the driver's
|
|
* access callback routine to handle the fault. The driver will at
|
|
* some point call ddi_mapdev_nointercept on this segment which will
|
|
* in turn call segmapdev_faultpages to fault in the pages.
|
|
* We will not fault in the pages of a mapdev segment now.
|
|
*/
|
|
mutex_enter(&sdp->lock);
|
|
|
|
/*
|
|
* This needs to be single threaded on a per segment bases
|
|
* due to the fact that we need to pass some data through the driver
|
|
* via the segment private data structure.
|
|
*
|
|
* We don't need to check the return value from cv_wait_sig() as it
|
|
* does not matter much if it returned due to a signal or due to a
|
|
* cv_signal() or cv_broadcast(). In either event we need to complete
|
|
* the mapping otherwise the processes will die with a SEGV.
|
|
*/
|
|
while (sdp->flags & SEGMAPDEV_FAULTING)
|
|
(void) cv_wait_sig(&sdp->wait, &sdp->lock);
|
|
|
|
/*
|
|
* Mark this mapdev segment as faulting so that we are single
|
|
* threaded through the driver and so that
|
|
* segmapdev_nointer knows if the segment it got
|
|
* from the access callback (via ddi_mapdev_nointercept)
|
|
* needs to be faulted or just marked as nointercept.
|
|
*/
|
|
sdp->flags |= SEGMAPDEV_FAULTING;
|
|
|
|
/*
|
|
* Check to see if we have per page interception information.
|
|
* If so is one of the pages that is faulting marked as
|
|
* intercept?
|
|
*
|
|
* If sdp->vpage_inter != NULL, then we assert that sdp->flags
|
|
* does not have SEGMAPDEV_INTER set. Else we don't assert anything
|
|
*/
|
|
ASSERT(((sdp->vpage_inter != NULL) ?
|
|
!(sdp->flags & SEGMAPDEV_INTER) : 1));
|
|
|
|
/*
|
|
* If we have per page interception information, see if the pages we
|
|
* are faulting on have their interception bit set.
|
|
*/
|
|
if (sdp->vpage_inter) {
|
|
for (a = addr; a < addr + len; a += PAGESIZE) {
|
|
page = seg_page(seg, a);
|
|
if (sdp->vpage_inter[page]) {
|
|
call_access = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we need to intercept this fault then call the
|
|
* driver access callback.
|
|
*/
|
|
if (((sdp->flags & SEGMAPDEV_INTER) ||
|
|
(call_access)) && (sdp->m_ops.mapdev_access)) {
|
|
|
|
/*
|
|
* Save away the hat, type of fault, and type of
|
|
* access so that segmapdev_faultpages will be able to
|
|
* handle the fault.
|
|
*/
|
|
sdp->hat = hat;
|
|
sdp->type = type;
|
|
sdp->rw = rw;
|
|
|
|
devctx = sdp->devctx;
|
|
mutex_exit(&sdp->lock);
|
|
|
|
/*
|
|
* If we are on an MP system with more than one cpu running
|
|
* and if a thread on some CPU already has the context, wait
|
|
* for it to finish if there is a hysteresis timeout.
|
|
*
|
|
* We don't need to check the return value from cv_wait_sig()
|
|
* as it does not matter much if it returned due to a signal
|
|
* or due to a cv_signal() or cv_broadcast(). In either event
|
|
* we need to complete the mapping otherwise the processes
|
|
* will die with a SEGV.
|
|
*
|
|
*/
|
|
if ((sdp->timeout_length > 0) && (ncpus > 1)) {
|
|
do_timeout = 1;
|
|
mutex_enter(&devctx->lock);
|
|
while (devctx->oncpu)
|
|
(void) cv_wait_sig(&devctx->cv, &devctx->lock);
|
|
devctx->oncpu = 1;
|
|
mutex_exit(&devctx->lock);
|
|
}
|
|
|
|
/*
|
|
* Call the access callback so that the driver can handle
|
|
* the fault.
|
|
*/
|
|
ret = (*sdp->m_ops.mapdev_access)
|
|
(seg, sdp->private_data,
|
|
(off_t)(addr - seg->s_base));
|
|
|
|
/*
|
|
* If mapdev_access() returned -1, then there was a hardware
|
|
* error so we need to convert the return value to something
|
|
* that trap() will understand. Otherwise, the return value
|
|
* is already a fault code generated by ddi_mapdev_intercept()
|
|
* or ddi_mapdev_nointercept().
|
|
*/
|
|
ASSERT(ret >= -1);
|
|
if (ret == -1)
|
|
ret = FC_HWERR;
|
|
|
|
/*
|
|
* Setup the timeout if we need to
|
|
*/
|
|
if (do_timeout) {
|
|
mutex_enter(&devctx->lock);
|
|
if (sdp->timeout_length > 0) {
|
|
devctx->timeout = timeout(segmapdev_ctxto,
|
|
(caddr_t)devctx, sdp->timeout_length);
|
|
} else {
|
|
/*
|
|
* We don't want to wait so set oncpu to
|
|
* 0 and wake up anyone waiting.
|
|
*/
|
|
devctx->oncpu = 0;
|
|
cv_signal(&devctx->cv);
|
|
}
|
|
mutex_exit(&devctx->lock);
|
|
}
|
|
|
|
mutex_enter(&sdp->lock);
|
|
|
|
} else {
|
|
/*
|
|
* Else the driver is not interested in being
|
|
* notified that this mapdev segment is faulting so
|
|
* just fault in the pages.
|
|
*/
|
|
ret = segmapdev_faultpages(hat, seg, addr, len,
|
|
type, rw);
|
|
}
|
|
|
|
/*
|
|
* Remove the faulting flag.
|
|
*/
|
|
|
|
sdp->flags &= ~SEGMAPDEV_FAULTING;
|
|
|
|
/*
|
|
* Wake up any other faults on this segment
|
|
* that are waiting to complete.
|
|
*/
|
|
cv_signal(&sdp->wait);
|
|
mutex_exit(&sdp->lock);
|
|
return (ret);
|
|
|
|
}
|
|
|
|
/*
|
|
* Asynchronous page fault. We simply do nothing since this
|
|
* entry point is not supposed to load up the translation.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static faultcode_t
|
|
segmapdev_faulta(struct seg *seg, caddr_t addr)
|
|
{
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
segmapdev_setprot(
|
|
register struct seg *seg,
|
|
register caddr_t addr,
|
|
register u_int len,
|
|
register u_int prot)
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register struct vpage *vp, *evp;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
if ((sdp->maxprot & prot) != prot)
|
|
return (EACCES); /* violated maxprot */
|
|
|
|
mutex_enter(&sdp->lock);
|
|
if (addr == seg->s_base && len == seg->s_size && sdp->pageprot == 0) {
|
|
if (sdp->prot == prot) {
|
|
mutex_exit(&sdp->lock);
|
|
return (0); /* all done */
|
|
}
|
|
sdp->prot = (u_char)prot;
|
|
} else {
|
|
sdp->pageprot = 1;
|
|
if (sdp->vpage == NULL) {
|
|
/*
|
|
* First time through setting per page permissions,
|
|
* initialize all the vpage structures to prot
|
|
*/
|
|
sdp->vpage = (struct vpage *)
|
|
kmem_zalloc(vpgtob(seg_pages(seg)), KM_SLEEP);
|
|
evp = &sdp->vpage[seg_pages(seg)];
|
|
for (vp = sdp->vpage; vp < evp; vp++)
|
|
vp->vp_prot = sdp->prot;
|
|
}
|
|
/*
|
|
* Now go change the needed vpages protections.
|
|
*/
|
|
evp = &sdp->vpage[seg_page(seg, addr + len)];
|
|
for (vp = &sdp->vpage[seg_page(seg, addr)]; vp < evp; vp++)
|
|
vp->vp_prot = prot;
|
|
}
|
|
mutex_exit(&sdp->lock);
|
|
|
|
if ((prot & PROT_WRITE) != 0 || (prot & ~PROT_USER) == PROT_NONE)
|
|
hat_unload(seg->s_as, addr, len, HAT_UNLOAD);
|
|
else
|
|
hat_chgprot(seg->s_as, addr, len, prot);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
segmapdev_checkprot(
|
|
register struct seg *seg,
|
|
register caddr_t addr,
|
|
register u_int len,
|
|
register u_int prot)
|
|
{
|
|
struct segmapdev_data *sdp = (struct segmapdev_data *)seg->s_data;
|
|
register struct vpage *vp, *evp;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
/*
|
|
* If segment protection can be used, simply check against them
|
|
*/
|
|
mutex_enter(&sdp->lock);
|
|
if (sdp->pageprot == 0) {
|
|
register int err;
|
|
|
|
err = ((sdp->prot & prot) != prot) ? EACCES : 0;
|
|
mutex_exit(&sdp->lock);
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* Have to check down to the vpage level
|
|
*/
|
|
evp = &sdp->vpage[seg_page(seg, addr + len)];
|
|
for (vp = &sdp->vpage[seg_page(seg, addr)]; vp < evp; vp++) {
|
|
if ((vp->vp_prot & prot) != prot) {
|
|
mutex_exit(&sdp->lock);
|
|
return (EACCES);
|
|
}
|
|
}
|
|
mutex_exit(&sdp->lock);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
segmapdev_getprot(
|
|
register struct seg *seg,
|
|
register caddr_t addr,
|
|
register u_int len,
|
|
register u_int *protv)
|
|
{
|
|
struct segmapdev_data *sdp = (struct segmapdev_data *)seg->s_data;
|
|
register u_int pgno;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
pgno = seg_page(seg, addr + len) - seg_page(seg, addr) + 1;
|
|
if (pgno != 0) {
|
|
mutex_enter(&sdp->lock);
|
|
if (sdp->pageprot == 0) {
|
|
do
|
|
protv[--pgno] = sdp->prot;
|
|
while (pgno != 0);
|
|
} else {
|
|
register u_int pgoff = seg_page(seg, addr);
|
|
|
|
do {
|
|
pgno--;
|
|
protv[pgno] = sdp->vpage[pgno + pgoff].vp_prot;
|
|
} while (pgno != 0);
|
|
}
|
|
mutex_exit(&sdp->lock);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static off_t
|
|
segmapdev_getoffset(register struct seg *seg, caddr_t addr)
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
return (sdp->offset);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
segmapdev_gettype(register struct seg *seg, caddr_t addr)
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
return ((sdp->flags & MAP_TYPE));
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
segmapdev_getvp(register struct seg *seg, caddr_t addr, struct vnode **vpp)
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
/*
|
|
* Note that this vp is the common_vp of the device, where the
|
|
* devpages are hung ..
|
|
*/
|
|
*vpp = VTOCVP(sdp->vp);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
segmapdev_badop(void)
|
|
{
|
|
cmn_err(CE_PANIC, "segmapdev_badop");
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
/*
|
|
* segmapdev pages are not in the cache, and thus can't really be controlled.
|
|
* Hence, syncs are simply always successful.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static int
|
|
segmapdev_sync(struct seg *seg, caddr_t addr, u_int len, int attr, u_int flags)
|
|
{
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* segmapdev pages are always "in core".
|
|
*/
|
|
/*ARGSUSED*/
|
|
static int
|
|
segmapdev_incore(struct seg *seg, caddr_t addr,
|
|
register u_int len, register char *vec)
|
|
{
|
|
register u_int v = 0;
|
|
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
for (len = (len + PAGEOFFSET) & PAGEMASK; len; len -= PAGESIZE,
|
|
v += PAGESIZE)
|
|
*vec++ = 1;
|
|
return (v);
|
|
}
|
|
|
|
/*
|
|
* segmapdev pages are not in the cache, and thus can't really be controlled.
|
|
* Hence, locks are simply always successful.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static int
|
|
segmapdev_lockop(struct seg *seg, caddr_t addr,
|
|
u_int len, int attr, int op, u_long *lockmap, size_t pos)
|
|
{
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* segmapdev pages are not in the cache, and thus can't really be controlled.
|
|
* Hence, advise is simply always successful.
|
|
*/
|
|
/*ARGSUSED*/
|
|
static int
|
|
segmapdev_advise(struct seg *seg, caddr_t addr, u_int len, int behav)
|
|
{
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* segmapdev pages are not dumped, so we just return
|
|
*/
|
|
/*ARGSUSED*/
|
|
static void
|
|
segmapdev_dump(struct seg *seg)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* segmapdev_inter()
|
|
*
|
|
* Called from ddi_mapdev_intercept
|
|
*
|
|
* Marks the mapdev segment or individual pages with in that segment as
|
|
* intercept and then unloads those pages to force a fault the next
|
|
* time they are accessed.
|
|
*/
|
|
static int
|
|
segmapdev_inter(struct seg *seg, caddr_t addr, off_t len)
|
|
{
|
|
register u_char *vpage_inter;
|
|
register caddr_t a;
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register u_char *evp;
|
|
|
|
mutex_enter(&sdp->lock);
|
|
|
|
/*
|
|
* If we are intercepting the entire segment, then mark the segment
|
|
* as intercept and remove any per page interception information.
|
|
*/
|
|
if ((addr == seg->s_base) && (len == seg->s_size)) {
|
|
sdp->flags |= SEGMAPDEV_INTER;
|
|
if (sdp->vpage_inter) {
|
|
kmem_free(sdp->vpage_inter, seg_pages(seg));
|
|
sdp->vpage_inter = NULL;
|
|
}
|
|
|
|
/*
|
|
* Unload the pages to force a fault next time they are
|
|
* accessed
|
|
*/
|
|
hat_unload(seg->s_as, seg->s_base, seg->s_size, HAT_UNLOAD);
|
|
|
|
mutex_exit(&sdp->lock);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* If we don't already have per page interception information
|
|
* allocate the per page data structure (array of bytes) and initialize
|
|
* them to the same value as the segment interception flag.
|
|
*/
|
|
if (!sdp->vpage_inter) {
|
|
sdp->vpage_inter = kmem_zalloc(seg_pages(seg), KM_SLEEP);
|
|
evp = &sdp->vpage_inter[seg_pages(seg)];
|
|
for (vpage_inter = sdp->vpage_inter; vpage_inter < evp;
|
|
vpage_inter++) {
|
|
*vpage_inter =
|
|
((sdp->flags & SEGMAPDEV_INTER) == SEGMAPDEV_INTER);
|
|
}
|
|
/*
|
|
* Turn off the segment intercept flag as we can't
|
|
* have both the segment intercept flag and per page
|
|
* intercept info at the same time.
|
|
*/
|
|
sdp->flags &= ~SEGMAPDEV_INTER;
|
|
}
|
|
|
|
/*
|
|
* For each page in addr -> addr+len mark those pages as
|
|
* interceptable.
|
|
*/
|
|
for (a = addr; a < (addr + len); a += PAGESIZE) {
|
|
sdp->vpage_inter[seg_page(seg, a)] = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* Unload the pages to force a fault next time they are
|
|
* accessed
|
|
*/
|
|
hat_unload(seg->s_as, addr, len, HAT_UNLOAD);
|
|
|
|
mutex_exit(&sdp->lock);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* segmapdev_faultpages
|
|
*
|
|
* Used to fault in mapdev segment pages instead of segmapdev_fault. Called
|
|
* from segmapdev_fault or segmapdev_nointer. This routine returns a
|
|
* faultcode_t. The faultcode_t value as a return value for segmapdev_fault.
|
|
*/
|
|
static int
|
|
segmapdev_faultpages(
|
|
struct hat *hat, /* the hat */
|
|
struct seg *seg, /* seg_dev of interest */
|
|
caddr_t addr, /* address in as */
|
|
u_int len, /* the length of the range */
|
|
enum fault_type type, /* type of fault */
|
|
enum seg_rw rw) /* type of access at fault */
|
|
{
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register caddr_t a;
|
|
register struct vpage *vpage;
|
|
register u_int *vpage_hat_flags;
|
|
int err, page;
|
|
cred_t *cr = CRED();
|
|
|
|
ASSERT(mutex_owned(&sdp->lock));
|
|
ASSERT(seg->s_as && AS_READ_HELD(seg->s_as, &seg->s_as->a_lock));
|
|
|
|
/*
|
|
* First handle the easy stuff
|
|
*/
|
|
if (type == F_SOFTUNLOCK) {
|
|
segmapdev_softunlock(hat, seg, addr, len, rw, cr);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* If we have the same protections for the entire segment,
|
|
* insure that the access being attempted is legitimate.
|
|
*
|
|
* Per page protections are checked in segmapdev_faultpage.
|
|
*/
|
|
if (sdp->pageprot == 0) {
|
|
u_int protchk;
|
|
|
|
switch (rw) {
|
|
case S_READ:
|
|
protchk = PROT_READ;
|
|
break;
|
|
case S_WRITE:
|
|
protchk = PROT_WRITE;
|
|
break;
|
|
case S_EXEC:
|
|
protchk = PROT_EXEC;
|
|
break;
|
|
case S_OTHER:
|
|
default:
|
|
protchk = PROT_READ | PROT_WRITE | PROT_EXEC;
|
|
break;
|
|
}
|
|
|
|
if ((sdp->prot & protchk) == 0)
|
|
return (FC_PROT); /* illegal access type */
|
|
}
|
|
|
|
page = seg_page(seg, addr);
|
|
|
|
if (sdp->vpage == NULL)
|
|
vpage = NULL;
|
|
else
|
|
vpage = &sdp->vpage[page];
|
|
|
|
if (sdp->pagehat_flags)
|
|
vpage_hat_flags = &sdp->vpage_hat_flags[page];
|
|
else
|
|
vpage_hat_flags = &sdp->hat_flags;
|
|
|
|
/*
|
|
* loop over the address range handling each fault
|
|
*
|
|
* XXXX This should eventually use hat_contig_devload. Currently it
|
|
* calls segmapdev_faultpage which calls hat_devload
|
|
*
|
|
* Per page protections are checked in segmapdev_faultpage.
|
|
*/
|
|
|
|
for (a = addr; a < addr + len; a += PAGESIZE) {
|
|
err = segmapdev_faultpage(hat, seg, a, vpage, vpage_hat_flags,
|
|
type, rw, cr);
|
|
if (err) {
|
|
if (type == F_SOFTLOCK && a > addr)
|
|
segmapdev_softunlock(hat, seg, addr,
|
|
(u_int)(a - addr), S_OTHER, cr);
|
|
/* FC_MAKE_ERR done by segmapdev_faultpage */
|
|
return (err);
|
|
}
|
|
|
|
if (sdp->pagehat_flags)
|
|
vpage_hat_flags++;
|
|
|
|
if (vpage != NULL)
|
|
vpage++;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* segmapdev_nointer()
|
|
*
|
|
* Called from ddi_mapdev_nointercept
|
|
*
|
|
* Marks the mapdev segment or individual pages with in that segment as
|
|
* no intercept and then fault in the pages if this segment is faulting.
|
|
* If this segment is not faulting, but the driver is just expressing
|
|
* dis-interest in these pages, just mark them as no intercept so that when
|
|
* do get a fault on these pages, we can just map them in without notifing
|
|
* the driver.
|
|
*/
|
|
static int
|
|
segmapdev_nointer(struct seg *seg, caddr_t addr, off_t len)
|
|
{
|
|
|
|
register u_char *vpage_inter;
|
|
register caddr_t a;
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register u_char *evp;
|
|
int err;
|
|
|
|
mutex_enter(&sdp->lock);
|
|
|
|
/*
|
|
* If we are not intercepting the entire segment, then mark the segment
|
|
* as no intercept and remove any per page interception information.
|
|
*/
|
|
if ((addr == seg->s_base) && (len == seg->s_size)) {
|
|
sdp->flags &= ~SEGMAPDEV_INTER;
|
|
if (sdp->vpage_inter) {
|
|
kmem_free(sdp->vpage_inter, seg_pages(seg));
|
|
sdp->vpage_inter = NULL;
|
|
}
|
|
} else {
|
|
|
|
/*
|
|
* If we don't already have per page interception information
|
|
* allocate the per page data structure (array of bytes) and
|
|
* initialize them to the same value as the segment
|
|
* interception flag.
|
|
*/
|
|
if (!sdp->vpage_inter) {
|
|
sdp->vpage_inter = kmem_zalloc(seg_pages(seg),
|
|
KM_SLEEP);
|
|
evp = &sdp->vpage_inter[seg_pages(seg)];
|
|
for (vpage_inter = sdp->vpage_inter; vpage_inter < evp;
|
|
vpage_inter++) {
|
|
*vpage_inter =
|
|
((sdp->flags & SEGMAPDEV_INTER) ==
|
|
SEGMAPDEV_INTER);
|
|
}
|
|
/*
|
|
* Turn off the segment intercept flag as we can't
|
|
* have both the segment intercept flag and per page
|
|
* intercept info at the same time.
|
|
*/
|
|
sdp->flags &= ~SEGMAPDEV_INTER;
|
|
}
|
|
|
|
/*
|
|
* For each page in addr -> addr+len mark those pages as
|
|
* not interceptable.
|
|
*/
|
|
for (a = addr; a < (addr + len); a += PAGESIZE) {
|
|
sdp->vpage_inter[seg_page(seg, a)] = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this segment is not faulting then the driver just wanted to
|
|
* mark this segment as nointercept. So just return.
|
|
*/
|
|
if (!(sdp->flags & SEGMAPDEV_FAULTING)) {
|
|
mutex_exit(&sdp->lock);
|
|
return (0);
|
|
}
|
|
|
|
|
|
err = segmapdev_faultpages(sdp->hat, seg, addr, len,
|
|
sdp->type, sdp->rw);
|
|
|
|
mutex_exit(&sdp->lock);
|
|
return (err);
|
|
}
|
|
|
|
/*
|
|
* segmapdev_set_access_attr()
|
|
*
|
|
* Called from ddi_mapdev_set_access_attr
|
|
*
|
|
* Marks the mapdev segment or individual pages within that segment with
|
|
* hat-related access attributes and then unloads the pages so they are
|
|
* faulted in with the new attributes.
|
|
*/
|
|
static int
|
|
segmapdev_set_access_attr(struct seg *seg, caddr_t addr, off_t len,
|
|
u_int hat_flags)
|
|
{
|
|
register u_int *vpage_hat_flags;
|
|
register caddr_t a;
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
register u_int *evp;
|
|
|
|
mutex_enter(&sdp->lock);
|
|
|
|
/*
|
|
* If we are assigning hat-flags for the entire segment, then mark the
|
|
* segment with hat-flags and remove any per page hat-flags information.
|
|
*/
|
|
if ((addr == seg->s_base) && (len == seg->s_size)) {
|
|
sdp->pagehat_flags = 0;
|
|
sdp->hat_flags = hat_flags;
|
|
if (sdp->vpage_hat_flags) {
|
|
kmem_free(sdp->vpage_hat_flags, seg_pages(seg));
|
|
sdp->vpage_hat_flags = NULL;
|
|
}
|
|
|
|
/*
|
|
* Unload the pages to force a fault next time they are
|
|
* accessed
|
|
*/
|
|
hat_unload(seg->s_as, seg->s_base, seg->s_size, HAT_UNLOAD);
|
|
|
|
mutex_exit(&sdp->lock);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* If we don't already have per page hat-flags information
|
|
* allocate the per page data structure (array of u_ints) and initialize
|
|
* them to the same value as the segment hat-flags flag.
|
|
*/
|
|
if (!sdp->vpage_hat_flags) {
|
|
sdp->vpage_hat_flags = kmem_zalloc(seg_pages(seg), KM_SLEEP);
|
|
evp = &sdp->vpage_hat_flags[seg_pages(seg)];
|
|
for (vpage_hat_flags = sdp->vpage_hat_flags;
|
|
vpage_hat_flags < evp; vpage_hat_flags++) {
|
|
*vpage_hat_flags = sdp->hat_flags;
|
|
}
|
|
|
|
/*
|
|
* Turn off the segment hat-flags flag as we can't
|
|
* have both the segment hat-flags flag and per page
|
|
* hat-flags info at the same time.
|
|
*/
|
|
sdp->pagehat_flags = 1;
|
|
sdp->hat_flags = 0;
|
|
}
|
|
|
|
/*
|
|
* For each page in addr -> addr+len mark those pages as
|
|
* interceptable.
|
|
*/
|
|
for (a = addr; a < (addr + len); a += PAGESIZE) {
|
|
sdp->vpage_hat_flags[seg_page(seg, a)] = hat_flags;
|
|
}
|
|
|
|
|
|
/*
|
|
* Unload the pages to force a fault next time they are
|
|
* accessed
|
|
*/
|
|
hat_unload(seg->s_as, addr, len, HAT_UNLOAD);
|
|
|
|
mutex_exit(&sdp->lock);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* XXXX The below code is to make this a loadable module. This code needs
|
|
* to be removed if seg_mapdev gets merged back into the kernel.
|
|
*/
|
|
|
|
#include <sys/modctl.h>
|
|
|
|
int _fini(void);
|
|
int _info(struct modinfo *modinfop);
|
|
int _init(void);
|
|
|
|
static struct modlmisc modlmisc = {
|
|
&mod_miscops,
|
|
"Mapdev Segment Device Driver",
|
|
};
|
|
|
|
static struct modlinkage modlinkage = {
|
|
MODREV_1,
|
|
&modlmisc,
|
|
NULL
|
|
};
|
|
|
|
int
|
|
_init(void)
|
|
{
|
|
int e;
|
|
|
|
mutex_init(&devctx_lock, "device context list lock",
|
|
MUTEX_DEFAULT, NULL);
|
|
if ((e = mod_install(&modlinkage)) != 0) {
|
|
mutex_destroy(&devctx_lock);
|
|
return (e);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
_fini(void)
|
|
{
|
|
#ifdef DEBUG_MAPDEV
|
|
int error;
|
|
|
|
mutex_enter(&devctx_lock)
|
|
if (devctx_list != NULL) {
|
|
mutex_exit(&devctx_lock);
|
|
return (EBUSY);
|
|
}
|
|
mutex_exit(&devctx_lock);
|
|
|
|
error = mod_remove(&modlinkage);
|
|
if (error != 0)
|
|
return (error);
|
|
mutex_destroy(&devctx_lock);
|
|
return (0);
|
|
#else
|
|
/*
|
|
* Don't allow module to be unloaded. We don't know if a new thread
|
|
* starts using devctx_list. And to check would cause a race
|
|
* condition.
|
|
*/
|
|
return (EBUSY);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
_info(struct modinfo *modinfop)
|
|
{
|
|
return (mod_info(&modlinkage, modinfop));
|
|
}
|
|
|
|
/*
|
|
* XXXX The below code is to make this a loadbale module. This code needs to
|
|
* be merged into sunddi.c if seg_mapdev gets merged back into the
|
|
* kernel.
|
|
*/
|
|
|
|
/*
|
|
* ddi_mapdev: Used by drivers who wish to be notified that a
|
|
* segment is faulting so that they can do context
|
|
* switching. Called from a drivers segmap(9E) routine.
|
|
*/
|
|
/*ARGSUSED*/
|
|
int
|
|
ddi_mapdev(dev_t dev, off_t off, struct as *as, caddr_t *addrp, off_t len,
|
|
u_int prot, u_int maxprot, u_int flags, cred_t *cred,
|
|
struct ddi_mapdev_ctl *m_ops, ddi_mapdev_handle_t *handle,
|
|
void *private_data)
|
|
{
|
|
struct segmapdev_crargs dev_a;
|
|
int (*mapfunc)(dev_t dev, off_t off, int prot);
|
|
int error;
|
|
|
|
if ((mapfunc = devopsp[getmajor(dev)]->devo_cb_ops->cb_mmap) ==
|
|
nodev)
|
|
return (ENODEV);
|
|
|
|
/*
|
|
* Its up to the driver to make sure that the mapping
|
|
* is in the correct range and to return an error if
|
|
* MAP_PRIVATE is not supported.
|
|
*/
|
|
|
|
as_rangelock(as);
|
|
if ((flags & MAP_FIXED) == 0) {
|
|
/*
|
|
* Pick an address w/o worrying about
|
|
* any vac alignment contraints.
|
|
*/
|
|
map_addr(addrp, len, (off_t)off, 0);
|
|
if (*addrp == NULL) {
|
|
as_rangeunlock(as);
|
|
return (ENOMEM);
|
|
}
|
|
} else {
|
|
/*
|
|
* User-specified address; blow away any previous mappings.
|
|
*/
|
|
(void) as_unmap(as, *addrp, len);
|
|
}
|
|
|
|
dev_a.mapfunc = mapfunc;
|
|
dev_a.dev = dev;
|
|
dev_a.offset = off;
|
|
dev_a.prot = (u_char)prot;
|
|
dev_a.maxprot = (u_char)maxprot;
|
|
dev_a.flags = flags;
|
|
dev_a.m_ops = m_ops;
|
|
dev_a.private_data = private_data;
|
|
dev_a.handle = handle;
|
|
|
|
error = as_map(as, *addrp, len, segmapdev_create, (caddr_t)&dev_a);
|
|
as_rangeunlock(as);
|
|
return (error);
|
|
|
|
}
|
|
|
|
/*
|
|
* ddi_mapdev_intercept:
|
|
* Marks a mapdev segment or pages if offset->offset+len
|
|
* is not the entire segment as intercept and unloads the
|
|
* pages in the range offset -> offset+len.
|
|
*/
|
|
int
|
|
ddi_mapdev_intercept(ddi_mapdev_handle_t handle, off_t offset, off_t len)
|
|
{
|
|
|
|
register struct seg *seg = (struct seg *)handle;
|
|
caddr_t addr;
|
|
off_t size;
|
|
|
|
if (offset > seg->s_size)
|
|
return (0);
|
|
|
|
/*
|
|
* Address and size must be page aligned. Len is set to the
|
|
* number of bytes in the number of pages that are required to
|
|
* support len. Offset is set to the byte offset of the first byte
|
|
* of the page that contains offset.
|
|
*/
|
|
len = mmu_ptob(mmu_btopr(len));
|
|
offset = mmu_ptob(mmu_btop(offset));
|
|
|
|
/*
|
|
* If len is == 0, then calculate the size by getting
|
|
* the number of bytes from offset to the end of the segment.
|
|
*/
|
|
if (len == 0)
|
|
size = seg->s_size - offset;
|
|
else {
|
|
size = len;
|
|
if ((offset+size) > seg->s_size)
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* The address is offset bytes from the base address of
|
|
* the segment.
|
|
*/
|
|
addr = offset + seg->s_base;
|
|
|
|
return (segmapdev_inter(seg, addr, size));
|
|
|
|
}
|
|
|
|
/*
|
|
* ddi_mapdev_nointercept:
|
|
* Marks a mapdev segment or pages if offset->offset+len
|
|
* is not the entire segment as nointercept and faults in
|
|
* the pages in the range offset -> offset+len.
|
|
*/
|
|
int
|
|
ddi_mapdev_nointercept(ddi_mapdev_handle_t handle, off_t offset, off_t len)
|
|
{
|
|
register struct seg *seg = (struct seg *)handle;
|
|
caddr_t addr;
|
|
off_t size;
|
|
|
|
if (offset > seg->s_size)
|
|
return (DDI_FAILURE);
|
|
|
|
/*
|
|
* Address and size must be page aligned. Len is set to the
|
|
* number of bytes in the number of pages that are required to
|
|
* support len. Offset is set to the byte offset of the first byte
|
|
* of the page that contains offset.
|
|
*/
|
|
len = mmu_ptob(mmu_btopr(len));
|
|
offset = mmu_ptob(mmu_btop(offset));
|
|
|
|
/*
|
|
* If len is == 0, then calculate the size by getting
|
|
* the number of bytes from offset to the end of the segment.
|
|
*/
|
|
if (len == 0)
|
|
size = seg->s_size - offset;
|
|
else {
|
|
size = len;
|
|
if ((offset+size) > seg->s_size)
|
|
return (FC_MAKE_ERR(EINVAL));
|
|
}
|
|
|
|
/*
|
|
* The address is offset bytes from the base address of
|
|
* the segment.
|
|
*/
|
|
addr = offset + seg->s_base;
|
|
|
|
return (segmapdev_nointer(seg, addr, size));
|
|
}
|
|
|
|
|
|
/*
|
|
* ddi_mapdev_set_device_acc_attr:
|
|
* Assigns a mapdev segment or pages, if offset->offset+len
|
|
* is not the entire segment, with the attributes defined
|
|
* by the access(9s) and then unloads the pages so they
|
|
* are faulted in with those attributes.
|
|
*/
|
|
int
|
|
ddi_mapdev_set_device_acc_attr(ddi_mapdev_handle_t handle, off_t offset,
|
|
off_t len, ddi_device_acc_attr_t *accattrp, uint_t rnumber)
|
|
{
|
|
register struct seg *seg = (struct seg *)handle;
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
caddr_t addr;
|
|
off_t size;
|
|
u_int hat_flags;
|
|
|
|
if (offset > seg->s_size)
|
|
return (ENXIO);
|
|
|
|
/*
|
|
* Address and size must be page aligned. Len is set to the
|
|
* number of bytes in the number of pages that are required to
|
|
* support len. Offset is set to the byte offset of the first byte
|
|
* of the page that contains offset.
|
|
*/
|
|
len = mmu_ptob(mmu_btopr(len));
|
|
offset = mmu_ptob(mmu_btop(offset));
|
|
|
|
/*
|
|
* If len is == 0, then calculate the size by getting
|
|
* the number of bytes from offset to the end of the segment.
|
|
*/
|
|
if (len == 0)
|
|
size = seg->s_size - offset;
|
|
else {
|
|
size = len;
|
|
if ((offset+size) > seg->s_size)
|
|
return (FC_MAKE_ERR(EINVAL));
|
|
}
|
|
|
|
/*
|
|
* The address is offset bytes from the base address of
|
|
* the segment.
|
|
*/
|
|
addr = offset + seg->s_base;
|
|
|
|
/*
|
|
* Check that this region is indeed mappable on this platform.
|
|
* Use the mapping function.
|
|
*/
|
|
if (ddi_device_mapping_check(sdp->dev, accattrp, rnumber, &hat_flags)
|
|
== -1)
|
|
return (ENXIO);
|
|
|
|
return (segmapdev_set_access_attr(seg, addr, size, hat_flags));
|
|
}
|
|
|
|
void
|
|
ddi_mapdev_set_keep_ctx(ddi_mapdev_handle_t handle, long ticks)
|
|
{
|
|
register struct seg *seg = (struct seg *)handle;
|
|
register struct segmapdev_data *sdp =
|
|
(struct segmapdev_data *)seg->s_data;
|
|
|
|
sdp->timeout_length = ticks;
|
|
}
|