xref: /titanic_52/usr/src/uts/common/io/physmem.c (revision dc32d872cbeb56532bcea030255db9cd79bac7da)
18b464eb8Smec /*
28b464eb8Smec  * CDDL HEADER START
38b464eb8Smec  *
48b464eb8Smec  * The contents of this file are subject to the terms of the
58b464eb8Smec  * Common Development and Distribution License (the "License").
68b464eb8Smec  * You may not use this file except in compliance with the License.
78b464eb8Smec  *
88b464eb8Smec  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
98b464eb8Smec  * or http://www.opensolaris.org/os/licensing.
108b464eb8Smec  * See the License for the specific language governing permissions
118b464eb8Smec  * and limitations under the License.
128b464eb8Smec  *
138b464eb8Smec  * When distributing Covered Code, include this CDDL HEADER in each
148b464eb8Smec  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
158b464eb8Smec  * If applicable, add the following below this CDDL HEADER, with the
168b464eb8Smec  * fields enclosed by brackets "[]" replaced with your own identifying
178b464eb8Smec  * information: Portions Copyright [yyyy] [name of copyright owner]
188b464eb8Smec  *
198b464eb8Smec  * CDDL HEADER END
208b464eb8Smec  */
218b464eb8Smec /*
2219397407SSherry Moore  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
238b464eb8Smec  * Use is subject to license terms.
248b464eb8Smec  */
258b464eb8Smec 
268b464eb8Smec 
278b464eb8Smec #include <sys/types.h>
288b464eb8Smec #include <sys/modctl.h>
298b464eb8Smec #include <sys/conf.h>
308b464eb8Smec #include <sys/ddi.h>
318b464eb8Smec #include <sys/sunddi.h>
328b464eb8Smec #include <sys/devops.h>
338b464eb8Smec #include <sys/stat.h>
348b464eb8Smec #include <sys/file.h>
358b464eb8Smec #include <sys/cred.h>
368b464eb8Smec #include <sys/policy.h>
378b464eb8Smec #include <sys/errno.h>
388b464eb8Smec #include <vm/seg_dev.h>
398b464eb8Smec #include <vm/seg_vn.h>
408b464eb8Smec #include <vm/page.h>
418b464eb8Smec #include <sys/fs/swapnode.h>
428b464eb8Smec #include <sys/sysmacros.h>
438b464eb8Smec #include <sys/fcntl.h>
448b464eb8Smec #include <sys/vmsystm.h>
458b464eb8Smec #include <sys/physmem.h>
46aa59c4cbSrsb #include <sys/vfs_opreg.h>
478b464eb8Smec 
488b464eb8Smec static dev_info_t		*physmem_dip = NULL;
498b464eb8Smec 
508b464eb8Smec /*
518b464eb8Smec  * Linked list element hanging off physmem_proc_hash below, which holds all
528b464eb8Smec  * the information for a given segment which has been setup for this process.
538b464eb8Smec  * This is a simple linked list as we are assuming that for a given process
548b464eb8Smec  * the setup ioctl will only be called a handful of times.  If this assumption
558b464eb8Smec  * changes in the future, a quicker to traverse data structure should be used.
568b464eb8Smec  */
578b464eb8Smec struct physmem_hash {
588b464eb8Smec 	struct physmem_hash *ph_next;
598b464eb8Smec 	uint64_t ph_base_pa;
608b464eb8Smec 	caddr_t ph_base_va;
618b464eb8Smec 	size_t ph_seg_len;
628b464eb8Smec 	struct vnode *ph_vnode;
638b464eb8Smec };
648b464eb8Smec 
658b464eb8Smec /*
668b464eb8Smec  * Hash of all of the processes which have setup mappings with the driver with
678b464eb8Smec  * pointers to per process data.
688b464eb8Smec  */
698b464eb8Smec struct physmem_proc_hash {
708b464eb8Smec 	struct proc *pph_proc;
718b464eb8Smec 	struct physmem_hash *pph_hash;
728b464eb8Smec 	struct physmem_proc_hash *pph_next;
738b464eb8Smec };
748b464eb8Smec 
758b464eb8Smec 
768b464eb8Smec /* Needs to be a power of two for simple hash algorithm */
778b464eb8Smec #define	PPH_SIZE	8
788b464eb8Smec struct physmem_proc_hash *pph[PPH_SIZE];
798b464eb8Smec 
808b464eb8Smec /*
818b464eb8Smec  * Lock which protects the pph hash above.  To add an element (either a new
828b464eb8Smec  * process or a new segment) the WRITE lock must be held.  To traverse the
838b464eb8Smec  * list, only a READ lock is needed.
848b464eb8Smec  */
858b464eb8Smec krwlock_t pph_rwlock;
868b464eb8Smec 
878b464eb8Smec #define	PHYSMEM_HASH(procp) ((int)((((uintptr_t)procp) >> 8) & (PPH_SIZE - 1)))
888b464eb8Smec 
898b464eb8Smec /*
908b464eb8Smec  * Need to keep a reference count of how many processes have the driver
918b464eb8Smec  * open to prevent it from disappearing.
928b464eb8Smec  */
938b464eb8Smec uint64_t physmem_vnodecnt;
948b464eb8Smec kmutex_t physmem_mutex;		/* protects phsymem_vnodecnt */
958b464eb8Smec 
968b464eb8Smec static int physmem_getpage(struct vnode *vp, offset_t off, size_t len,
978b464eb8Smec     uint_t *protp, page_t *pl[], size_t plsz, struct seg *seg, caddr_t addr,
98da6c28aaSamw     enum seg_rw rw, struct cred *cr, caller_context_t *ct);
998b464eb8Smec 
1008b464eb8Smec static int physmem_addmap(struct vnode *vp, offset_t off, struct as *as,
1018b464eb8Smec     caddr_t addr, size_t len, uchar_t prot, uchar_t maxprot, uint_t flags,
102da6c28aaSamw     struct cred *cred, caller_context_t *ct);
1038b464eb8Smec 
1048b464eb8Smec static int physmem_delmap(struct vnode *vp, offset_t off, struct as *as,
1058b464eb8Smec     caddr_t addr, size_t len, uint_t prot, uint_t maxprot, uint_t flags,
106da6c28aaSamw     struct cred *cred, caller_context_t *ct);
1078b464eb8Smec 
108da6c28aaSamw static void physmem_inactive(vnode_t *vp, cred_t *crp, caller_context_t *ct);
1098b464eb8Smec 
1108b464eb8Smec const fs_operation_def_t physmem_vnodeops_template[] = {
111aa59c4cbSrsb 	VOPNAME_GETPAGE,	{ .vop_getpage = physmem_getpage },
112aa59c4cbSrsb 	VOPNAME_ADDMAP,		{ .vop_addmap = physmem_addmap },
113aa59c4cbSrsb 	VOPNAME_DELMAP,		{ .vop_delmap = physmem_delmap },
114aa59c4cbSrsb 	VOPNAME_INACTIVE,	{ .vop_inactive = physmem_inactive },
1158b464eb8Smec 	NULL,			NULL
1168b464eb8Smec };
1178b464eb8Smec 
1188b464eb8Smec vnodeops_t *physmem_vnodeops = NULL;
1198b464eb8Smec 
1208b464eb8Smec /*
1218b464eb8Smec  * Removes the current process from the hash if the process has no more
1228b464eb8Smec  * physmem segments active.
1238b464eb8Smec  */
1248b464eb8Smec void
1258b464eb8Smec physmem_remove_hash_proc()
1268b464eb8Smec {
1278b464eb8Smec 	int index;
1288b464eb8Smec 	struct physmem_proc_hash **walker;
1298b464eb8Smec 	struct physmem_proc_hash *victim = NULL;
1308b464eb8Smec 
1318b464eb8Smec 	index = PHYSMEM_HASH(curproc);
1328b464eb8Smec 	rw_enter(&pph_rwlock, RW_WRITER);
1338b464eb8Smec 	walker = &pph[index];
1348b464eb8Smec 	while (*walker != NULL) {
1358b464eb8Smec 		if ((*walker)->pph_proc == curproc &&
1368b464eb8Smec 		    (*walker)->pph_hash == NULL) {
1378b464eb8Smec 			victim = *walker;
1388b464eb8Smec 			*walker = victim->pph_next;
1398b464eb8Smec 			break;
1408b464eb8Smec 		}
1418b464eb8Smec 		walker = &((*walker)->pph_next);
1428b464eb8Smec 	}
1438b464eb8Smec 	rw_exit(&pph_rwlock);
1448b464eb8Smec 	if (victim != NULL)
1458b464eb8Smec 		kmem_free(victim, sizeof (struct physmem_proc_hash));
1468b464eb8Smec }
1478b464eb8Smec 
1488b464eb8Smec /*
1498b464eb8Smec  * Add a new entry to the hash for the given process to cache the
1508b464eb8Smec  * address ranges that it is working on.  If this is the first hash
1518b464eb8Smec  * item to be added for this process, we will create the head pointer
1528b464eb8Smec  * for this process.
1538b464eb8Smec  * Returns 0 on success, ERANGE when the physical address is already in the
154cd64d6e9Smec  * hash.
1558b464eb8Smec  */
1568b464eb8Smec int
1578b464eb8Smec physmem_add_hash(struct physmem_hash *php)
1588b464eb8Smec {
1598b464eb8Smec 	int index;
1608b464eb8Smec 	struct physmem_proc_hash *iterator;
1618b464eb8Smec 	struct physmem_proc_hash *newp = NULL;
1628b464eb8Smec 	struct physmem_hash *temp;
1638b464eb8Smec 	int ret = 0;
1648b464eb8Smec 
1658b464eb8Smec 	index = PHYSMEM_HASH(curproc);
1668b464eb8Smec 
1678b464eb8Smec insert:
1688b464eb8Smec 	rw_enter(&pph_rwlock, RW_WRITER);
1698b464eb8Smec 	iterator = pph[index];
1708b464eb8Smec 	while (iterator != NULL) {
1718b464eb8Smec 		if (iterator->pph_proc == curproc) {
1728b464eb8Smec 			/*
1738b464eb8Smec 			 * check to make sure a single process does not try to
1748b464eb8Smec 			 * map the same region twice.
1758b464eb8Smec 			 */
1768b464eb8Smec 			for (temp = iterator->pph_hash; temp != NULL;
1778b464eb8Smec 			    temp = temp->ph_next) {
1788b464eb8Smec 				if ((php->ph_base_pa >= temp->ph_base_pa &&
1798b464eb8Smec 				    php->ph_base_pa < temp->ph_base_pa +
1808b464eb8Smec 				    temp->ph_seg_len) ||
1818b464eb8Smec 				    (temp->ph_base_pa >= php->ph_base_pa &&
1828b464eb8Smec 				    temp->ph_base_pa < php->ph_base_pa +
1838b464eb8Smec 				    php->ph_seg_len)) {
1848b464eb8Smec 					ret = ERANGE;
1858b464eb8Smec 					break;
1868b464eb8Smec 				}
1878b464eb8Smec 			}
1888b464eb8Smec 			if (ret == 0) {
1898b464eb8Smec 				php->ph_next = iterator->pph_hash;
1908b464eb8Smec 				iterator->pph_hash = php;
1918b464eb8Smec 			}
1928b464eb8Smec 			rw_exit(&pph_rwlock);
1938b464eb8Smec 			/* Need to check for two threads in sync */
1948b464eb8Smec 			if (newp != NULL)
1958b464eb8Smec 				kmem_free(newp, sizeof (*newp));
1968b464eb8Smec 			return (ret);
1978b464eb8Smec 		}
1988b464eb8Smec 		iterator = iterator->pph_next;
1998b464eb8Smec 	}
2008b464eb8Smec 
2018b464eb8Smec 	if (newp != NULL) {
2028b464eb8Smec 		newp->pph_proc = curproc;
2038b464eb8Smec 		newp->pph_next = pph[index];
2048b464eb8Smec 		newp->pph_hash = php;
2058b464eb8Smec 		php->ph_next = NULL;
2068b464eb8Smec 		pph[index] = newp;
2078b464eb8Smec 		rw_exit(&pph_rwlock);
2088b464eb8Smec 		return (0);
2098b464eb8Smec 	}
2108b464eb8Smec 
2118b464eb8Smec 	rw_exit(&pph_rwlock);
2128b464eb8Smec 	/* Dropped the lock so we could use KM_SLEEP */
2138b464eb8Smec 	newp = kmem_zalloc(sizeof (struct physmem_proc_hash), KM_SLEEP);
2148b464eb8Smec 	goto insert;
2158b464eb8Smec }
2168b464eb8Smec 
2178b464eb8Smec /*
2188b464eb8Smec  * Will return the pointer to the physmem_hash struct if the setup routine
2198b464eb8Smec  * has previously been called for this memory.
2208b464eb8Smec  * Returns NULL on failure.
2218b464eb8Smec  */
2228b464eb8Smec struct physmem_hash *
2238b464eb8Smec physmem_get_hash(uint64_t req_paddr, size_t len, proc_t *procp)
2248b464eb8Smec {
2258b464eb8Smec 	int index;
2268b464eb8Smec 	struct physmem_proc_hash *proc_hp;
2278b464eb8Smec 	struct physmem_hash *php;
2288b464eb8Smec 
2298b464eb8Smec 	ASSERT(rw_lock_held(&pph_rwlock));
2308b464eb8Smec 
2318b464eb8Smec 	index = PHYSMEM_HASH(procp);
2328b464eb8Smec 	proc_hp = pph[index];
2338b464eb8Smec 	while (proc_hp != NULL) {
2348b464eb8Smec 		if (proc_hp->pph_proc == procp) {
2358b464eb8Smec 			php = proc_hp->pph_hash;
2368b464eb8Smec 			while (php != NULL) {
2378b464eb8Smec 				if ((req_paddr >= php->ph_base_pa) &&
2388b464eb8Smec 				    (req_paddr + len <=
2398b464eb8Smec 				    php->ph_base_pa + php->ph_seg_len)) {
2408b464eb8Smec 					return (php);
2418b464eb8Smec 				}
2428b464eb8Smec 				php = php->ph_next;
2438b464eb8Smec 			}
2448b464eb8Smec 		}
2458b464eb8Smec 		proc_hp = proc_hp->pph_next;
2468b464eb8Smec 	}
2478b464eb8Smec 	return (NULL);
2488b464eb8Smec }
2498b464eb8Smec 
2508b464eb8Smec int
2518b464eb8Smec physmem_validate_cookie(uint64_t p_cookie)
2528b464eb8Smec {
2538b464eb8Smec 	int index;
2548b464eb8Smec 	struct physmem_proc_hash *proc_hp;
2558b464eb8Smec 	struct physmem_hash *php;
2568b464eb8Smec 
2578b464eb8Smec 	ASSERT(rw_lock_held(&pph_rwlock));
2588b464eb8Smec 
2598b464eb8Smec 	index = PHYSMEM_HASH(curproc);
2608b464eb8Smec 	proc_hp = pph[index];
2618b464eb8Smec 	while (proc_hp != NULL) {
2628b464eb8Smec 		if (proc_hp->pph_proc == curproc) {
2638b464eb8Smec 			php = proc_hp->pph_hash;
2648b464eb8Smec 			while (php != NULL) {
2658b464eb8Smec 				if ((uint64_t)(uintptr_t)php == p_cookie) {
2668b464eb8Smec 					return (1);
2678b464eb8Smec 				}
2688b464eb8Smec 				php = php->ph_next;
2698b464eb8Smec 			}
2708b464eb8Smec 		}
2718b464eb8Smec 		proc_hp = proc_hp->pph_next;
2728b464eb8Smec 	}
2738b464eb8Smec 	return (0);
2748b464eb8Smec }
2758b464eb8Smec 
2768b464eb8Smec /*
2778b464eb8Smec  * Remove the given vnode from the pph hash.  If it exists in the hash the
2788b464eb8Smec  * process still has to be around as the vnode is obviously still around and
2798b464eb8Smec  * since it's a physmem vnode, it must be in the hash.
2808b464eb8Smec  * If it is not in the hash that must mean that the setup ioctl failed.
2818b464eb8Smec  * Return 0 in this instance, 1 if it is in the hash.
2828b464eb8Smec  */
2838b464eb8Smec int
2848b464eb8Smec physmem_remove_vnode_hash(vnode_t *vp)
2858b464eb8Smec {
2868b464eb8Smec 	int index;
2878b464eb8Smec 	struct physmem_proc_hash *proc_hp;
2888b464eb8Smec 	struct physmem_hash **phpp;
2898b464eb8Smec 	struct physmem_hash *victim;
2908b464eb8Smec 
2918b464eb8Smec 	index = PHYSMEM_HASH(curproc);
2928b464eb8Smec 	/* synchronize with the map routine */
2938b464eb8Smec 	rw_enter(&pph_rwlock, RW_WRITER);
2948b464eb8Smec 	proc_hp = pph[index];
2958b464eb8Smec 	while (proc_hp != NULL) {
2968b464eb8Smec 		if (proc_hp->pph_proc == curproc) {
2978b464eb8Smec 			phpp = &proc_hp->pph_hash;
2988b464eb8Smec 			while (*phpp != NULL) {
2998b464eb8Smec 				if ((*phpp)->ph_vnode == vp) {
3008b464eb8Smec 					victim = *phpp;
3018b464eb8Smec 					*phpp = victim->ph_next;
3028b464eb8Smec 
3038b464eb8Smec 					rw_exit(&pph_rwlock);
3048b464eb8Smec 					kmem_free(victim, sizeof (*victim));
3058b464eb8Smec 					return (1);
3068b464eb8Smec 				}
3078b464eb8Smec 				phpp = &(*phpp)->ph_next;
3088b464eb8Smec 			}
3098b464eb8Smec 		}
3108b464eb8Smec 		proc_hp = proc_hp->pph_next;
3118b464eb8Smec 	}
3128b464eb8Smec 	rw_exit(&pph_rwlock);
3138b464eb8Smec 
3148b464eb8Smec 	/* not found */
3158b464eb8Smec 	return (0);
3168b464eb8Smec }
3178b464eb8Smec 
3188b464eb8Smec int
3198b464eb8Smec physmem_setup_vnops()
3208b464eb8Smec {
3218b464eb8Smec 	int error;
3228b464eb8Smec 	char *name = "physmem";
3238b464eb8Smec 	if (physmem_vnodeops != NULL)
3248b464eb8Smec 		cmn_err(CE_PANIC, "physmem vnodeops already set\n");
3258b464eb8Smec 	error = vn_make_ops(name, physmem_vnodeops_template, &physmem_vnodeops);
3268b464eb8Smec 	if (error != 0) {
3278b464eb8Smec 		cmn_err(CE_WARN, "physmem_setup_vnops: bad vnode ops template");
3288b464eb8Smec 	}
3298b464eb8Smec 	return (error);
3308b464eb8Smec }
3318b464eb8Smec 
3328b464eb8Smec /*
3338b464eb8Smec  * The guts of the PHYSMEM_SETUP ioctl.
3348b464eb8Smec  * Create a segment in the address space with the specified parameters.
3358b464eb8Smec  * If pspp->user_va is NULL, as_gap will be used to find an appropriate VA.
336da6c28aaSamw  * We do not do bounds checking on the requested physical addresses, if they
3378b464eb8Smec  * do not exist in the system, they will not be mappable.
3388b464eb8Smec  * Returns 0 on success with the following error codes on failure:
3398b464eb8Smec  *	ENOMEM - The VA range requested was already mapped if pspp->user_va is
3408b464eb8Smec  *		non-NULL or the system was unable to find enough VA space for
3418b464eb8Smec  *		the desired length if user_va was NULL>
3428b464eb8Smec  *	EINVAL - The requested PA, VA, or length was not PAGESIZE aligned.
3438b464eb8Smec  */
3448b464eb8Smec int
3458b464eb8Smec physmem_setup_addrs(struct physmem_setup_param *pspp)
3468b464eb8Smec {
3478b464eb8Smec 	struct as *as = curproc->p_as;
3488b464eb8Smec 	struct segvn_crargs vn_a;
3498b464eb8Smec 	int ret = 0;
3508b464eb8Smec 	uint64_t base_pa;
3518b464eb8Smec 	size_t len;
3528b464eb8Smec 	caddr_t uvaddr;
3538b464eb8Smec 	struct vnode *vp;
3548b464eb8Smec 	struct physmem_hash *php;
3558b464eb8Smec 
3568b464eb8Smec 	ASSERT(pspp != NULL);
3578b464eb8Smec 	base_pa = pspp->req_paddr;
3588b464eb8Smec 	len = pspp->len;
3598b464eb8Smec 	uvaddr = (caddr_t)(uintptr_t)pspp->user_va;
3608b464eb8Smec 
3618b464eb8Smec 	/* Sanity checking */
3628b464eb8Smec 	if (!IS_P2ALIGNED(base_pa, PAGESIZE))
3638b464eb8Smec 		return (EINVAL);
3648b464eb8Smec 	if (!IS_P2ALIGNED(len, PAGESIZE))
3658b464eb8Smec 		return (EINVAL);
3668b464eb8Smec 	if (uvaddr != NULL && !IS_P2ALIGNED(uvaddr, PAGESIZE))
3678b464eb8Smec 		return (EINVAL);
3688b464eb8Smec 
3698b464eb8Smec 	php = kmem_zalloc(sizeof (struct physmem_hash), KM_SLEEP);
3708b464eb8Smec 
3718b464eb8Smec 	/* Need to bump vnode count so that the driver can not be unloaded */
3728b464eb8Smec 	mutex_enter(&physmem_mutex);
3738b464eb8Smec 	physmem_vnodecnt++;
3748b464eb8Smec 	mutex_exit(&physmem_mutex);
3758b464eb8Smec 
3768b464eb8Smec 	vp = vn_alloc(KM_SLEEP);
3778b464eb8Smec 	ASSERT(vp != NULL);	/* SLEEP can't return NULL */
3788b464eb8Smec 	vn_setops(vp, physmem_vnodeops);
3798b464eb8Smec 
3808b464eb8Smec 	php->ph_vnode = vp;
3818b464eb8Smec 
3828b464eb8Smec 	vn_a.vp = vp;
3838b464eb8Smec 	vn_a.offset = (u_offset_t)base_pa;
3848b464eb8Smec 	vn_a.type = MAP_SHARED;
3858b464eb8Smec 	vn_a.prot = PROT_ALL;
3868b464eb8Smec 	vn_a.maxprot = PROT_ALL;
3878b464eb8Smec 	vn_a.flags = 0;
3888b464eb8Smec 	vn_a.cred = NULL;
3898b464eb8Smec 	vn_a.amp = NULL;
3908b464eb8Smec 	vn_a.szc = 0;
3918b464eb8Smec 	vn_a.lgrp_mem_policy_flags = 0;
3928b464eb8Smec 
3938b464eb8Smec 	as_rangelock(as);
3948b464eb8Smec 	if (uvaddr != NULL) {
3958b464eb8Smec 		if (as_gap(as, len, &uvaddr, &len, AH_LO, NULL) == -1) {
3968b464eb8Smec 			ret = ENOMEM;
3978b464eb8Smec fail:
3988b464eb8Smec 			as_rangeunlock(as);
3998b464eb8Smec 			vn_free(vp);
4008b464eb8Smec 			kmem_free(php, sizeof (*php));
4018b464eb8Smec 			mutex_enter(&physmem_mutex);
4028b464eb8Smec 			physmem_vnodecnt--;
4038b464eb8Smec 			mutex_exit(&physmem_mutex);
4048b464eb8Smec 			return (ret);
4058b464eb8Smec 		}
4068b464eb8Smec 	} else {
4078b464eb8Smec 		/* We pick the address for the user */
4088b464eb8Smec 		map_addr(&uvaddr, len, 0, 1, 0);
4098b464eb8Smec 		if (uvaddr == NULL) {
4108b464eb8Smec 			ret = ENOMEM;
4118b464eb8Smec 			goto fail;
4128b464eb8Smec 		}
4138b464eb8Smec 	}
4148b464eb8Smec 	ret = as_map(as, uvaddr, len, segvn_create, &vn_a);
4158b464eb8Smec 
4168b464eb8Smec 	if (ret == 0) {
417cd64d6e9Smec 		as_rangeunlock(as);
4188b464eb8Smec 		php->ph_base_pa = base_pa;
4198b464eb8Smec 		php->ph_base_va = uvaddr;
4208b464eb8Smec 		php->ph_seg_len = len;
4218b464eb8Smec 		pspp->user_va = (uint64_t)(uintptr_t)uvaddr;
4228b464eb8Smec 		pspp->cookie = (uint64_t)(uintptr_t)php;
4238b464eb8Smec 		ret = physmem_add_hash(php);
4248b464eb8Smec 		if (ret == 0)
4258b464eb8Smec 			return (0);
426cd64d6e9Smec 
427cd64d6e9Smec 		/* Note that the call to as_unmap will free the vnode */
4288b464eb8Smec 		(void) as_unmap(as, uvaddr, len);
429cd64d6e9Smec 		kmem_free(php, sizeof (*php));
4308b464eb8Smec 		return (ret);
4318b464eb8Smec 	}
4328b464eb8Smec 
4338b464eb8Smec 	goto fail;
4348b464eb8Smec 	/*NOTREACHED*/
4358b464eb8Smec }
4368b464eb8Smec 
4378b464eb8Smec /*
4388b464eb8Smec  * The guts of the PHYSMEM_MAP ioctl.
4398b464eb8Smec  * Map the given PA to the appropriate VA if PHYSMEM_SETUP ioctl has already
4408b464eb8Smec  * been called for this PA range.
4418b464eb8Smec  * Returns 0 on success with the following error codes on failure:
4428b464eb8Smec  *	EPERM - The requested page is long term locked, and thus repeated
4438b464eb8Smec  *		requests to allocate this page will likely fail.
4448b464eb8Smec  *	EAGAIN - The requested page could not be allocated, but it is believed
4458b464eb8Smec  *		that future attempts could succeed.
4468b464eb8Smec  *	ENOMEM - There was not enough free memory in the system to safely
4478b464eb8Smec  *		map the requested page.
4488b464eb8Smec  *	EINVAL - The requested paddr was not PAGESIZE aligned or the
4498b464eb8Smec  *		PHYSMEM_SETUP ioctl was not called for this page.
4508b464eb8Smec  *	ENOENT - The requested page was iniside the kernel cage, and the
4518b464eb8Smec  *		PHYSMEM_CAGE flag was not set.
4528b464eb8Smec  *	EBUSY - The requested page is retired and the PHYSMEM_RETIRE flag
4538b464eb8Smec  *		was not set.
4548b464eb8Smec  */
4558b464eb8Smec static int
4568b464eb8Smec physmem_map_addrs(struct physmem_map_param *pmpp)
4578b464eb8Smec {
4588b464eb8Smec 	caddr_t uvaddr;
4598b464eb8Smec 	page_t *pp;
4608b464eb8Smec 	uint64_t req_paddr;
4618b464eb8Smec 	struct vnode *vp;
4628b464eb8Smec 	int ret = 0;
4638b464eb8Smec 	struct physmem_hash *php;
4648b464eb8Smec 	uint_t flags = 0;
4658b464eb8Smec 
4668b464eb8Smec 	ASSERT(pmpp != NULL);
4678b464eb8Smec 	req_paddr = pmpp->req_paddr;
4688b464eb8Smec 
4698b464eb8Smec 	if (!IS_P2ALIGNED(req_paddr, PAGESIZE))
4708b464eb8Smec 		return (EINVAL);
4718b464eb8Smec 	/* Find the vnode for this map request */
4728b464eb8Smec 	rw_enter(&pph_rwlock, RW_READER);
4738b464eb8Smec 	php = physmem_get_hash(req_paddr, PAGESIZE, curproc);
4748b464eb8Smec 	if (php == NULL) {
4758b464eb8Smec 		rw_exit(&pph_rwlock);
4768b464eb8Smec 		return (EINVAL);
4778b464eb8Smec 	}
4788b464eb8Smec 	vp = php->ph_vnode;
4798b464eb8Smec 	uvaddr = php->ph_base_va + (req_paddr - php->ph_base_pa);
4808b464eb8Smec 	rw_exit(&pph_rwlock);
4818b464eb8Smec 
4828b464eb8Smec 	pp = page_numtopp_nolock(btop((size_t)req_paddr));
4838b464eb8Smec 	if (pp == NULL) {
4848b464eb8Smec 		pmpp->ret_va = NULL;
4858b464eb8Smec 		return (EPERM);
4868b464eb8Smec 	}
4878b464eb8Smec 
4888b464eb8Smec 	/*
4898b464eb8Smec 	 * Check to see if page already mapped correctly.  This can happen
4908b464eb8Smec 	 * when we failed to capture a page previously and it was captured
4918b464eb8Smec 	 * asynchronously for us.  Return success in this case.
4928b464eb8Smec 	 */
4938b464eb8Smec 	if (pp->p_vnode == vp) {
4948b464eb8Smec 		ASSERT(pp->p_offset == (u_offset_t)req_paddr);
4958b464eb8Smec 		pmpp->ret_va = (uint64_t)(uintptr_t)uvaddr;
4968b464eb8Smec 		return (0);
4978b464eb8Smec 	}
4988b464eb8Smec 
4998b464eb8Smec 	/*
5008b464eb8Smec 	 * physmem should be responsible for checking for cage
5018b464eb8Smec 	 * and prom pages.
5028b464eb8Smec 	 */
5038b464eb8Smec 	if (pmpp->flags & PHYSMEM_CAGE)
5048b464eb8Smec 		flags = CAPTURE_GET_CAGE;
5058b464eb8Smec 	if (pmpp->flags & PHYSMEM_RETIRED)
5068b464eb8Smec 		flags |= CAPTURE_GET_RETIRED;
5078b464eb8Smec 
5088b464eb8Smec 	ret = page_trycapture(pp, 0, flags | CAPTURE_PHYSMEM, curproc);
5098b464eb8Smec 
5108b464eb8Smec 	if (ret != 0) {
5118b464eb8Smec 		pmpp->ret_va = NULL;
5128b464eb8Smec 		return (ret);
5138b464eb8Smec 	} else {
5148b464eb8Smec 		pmpp->ret_va = (uint64_t)(uintptr_t)uvaddr;
5158b464eb8Smec 		return (0);
5168b464eb8Smec 	}
5178b464eb8Smec }
5188b464eb8Smec 
5198b464eb8Smec /*
5208b464eb8Smec  * Map the given page into the process's address space if possible.
5218b464eb8Smec  * We actually only hash the page in on the correct vnode as the page
5228b464eb8Smec  * will be mapped via segvn_pagefault.
5238b464eb8Smec  * returns 0 on success
5248b464eb8Smec  * returns 1 if there is no need to map this page anymore (process exited)
5258b464eb8Smec  * returns -1 if we failed to map the page.
5268b464eb8Smec  */
5278b464eb8Smec int
5288b464eb8Smec map_page_proc(page_t *pp, void *arg, uint_t flags)
5298b464eb8Smec {
5308b464eb8Smec 	struct vnode *vp;
5318b464eb8Smec 	proc_t *procp = (proc_t *)arg;
5328b464eb8Smec 	int ret;
5338b464eb8Smec 	u_offset_t paddr = (u_offset_t)ptob(pp->p_pagenum);
5348b464eb8Smec 	struct physmem_hash *php;
5358b464eb8Smec 
5368b464eb8Smec 	ASSERT(pp != NULL);
5378b464eb8Smec 
5388b464eb8Smec 	/*
5398b464eb8Smec 	 * Check against availrmem to make sure that we're not low on memory.
5408b464eb8Smec 	 * We check again here as ASYNC requests do not do this check elsewhere.
5418b464eb8Smec 	 * We return 1 as we don't want the page to have the PR_CAPTURE bit
5428b464eb8Smec 	 * set or be on the page capture hash.
5438b464eb8Smec 	 */
5448b464eb8Smec 	if (swapfs_minfree > availrmem + 1) {
5458b464eb8Smec 		page_free(pp, 1);
5468b464eb8Smec 		return (1);
5478b464eb8Smec 	}
5488b464eb8Smec 
5498b464eb8Smec 	/*
5508b464eb8Smec 	 * If this is an asynchronous request for the current process,
5518b464eb8Smec 	 * we can not map the page as it's possible that we are also in the
5528b464eb8Smec 	 * process of unmapping the page which could result in a deadlock
5538b464eb8Smec 	 * with the as lock.
5548b464eb8Smec 	 */
5558b464eb8Smec 	if ((flags & CAPTURE_ASYNC) && (curproc == procp)) {
5568b464eb8Smec 		page_free(pp, 1);
5578b464eb8Smec 		return (-1);
5588b464eb8Smec 	}
5598b464eb8Smec 
5608b464eb8Smec 	/* only return zeroed out pages */
5618b464eb8Smec 	pagezero(pp, 0, PAGESIZE);
5628b464eb8Smec 
5638b464eb8Smec 	rw_enter(&pph_rwlock, RW_READER);
5648b464eb8Smec 	php = physmem_get_hash(paddr, PAGESIZE, procp);
5658b464eb8Smec 	if (php == NULL) {
5668b464eb8Smec 		rw_exit(&pph_rwlock);
5678b464eb8Smec 		/*
5688b464eb8Smec 		 * Free the page as there is no longer a valid outstanding
5698b464eb8Smec 		 * request for this page.
5708b464eb8Smec 		 */
5718b464eb8Smec 		page_free(pp, 1);
5728b464eb8Smec 		return (1);
5738b464eb8Smec 	}
5748b464eb8Smec 
5758b464eb8Smec 	vp = php->ph_vnode;
5768b464eb8Smec 
5778b464eb8Smec 	/*
5788b464eb8Smec 	 * We need to protect against a possible deadlock here where we own
5798b464eb8Smec 	 * the vnode page hash mutex and want to acquire it again as there
5808b464eb8Smec 	 * are locations in the code, where we unlock a page while holding
5818b464eb8Smec 	 * the mutex which can lead to the page being captured and eventually
5828b464eb8Smec 	 * end up here.
5838b464eb8Smec 	 */
5848b464eb8Smec 	if (mutex_owned(page_vnode_mutex(vp))) {
5858b464eb8Smec 		rw_exit(&pph_rwlock);
5868b464eb8Smec 		page_free(pp, 1);
5878b464eb8Smec 		return (-1);
5888b464eb8Smec 	}
5898b464eb8Smec 
5908b464eb8Smec 	ret = page_hashin(pp, vp, paddr, NULL);
5918b464eb8Smec 	rw_exit(&pph_rwlock);
5928b464eb8Smec 	if (ret == 0) {
5938b464eb8Smec 		page_free(pp, 1);
5948b464eb8Smec 		return (-1);
5958b464eb8Smec 	}
5968b464eb8Smec 
5978b464eb8Smec 	page_downgrade(pp);
5988b464eb8Smec 
5998b464eb8Smec 	mutex_enter(&freemem_lock);
6008b464eb8Smec 	availrmem--;
6018b464eb8Smec 	mutex_exit(&freemem_lock);
6028b464eb8Smec 
6038b464eb8Smec 	return (0);
6048b464eb8Smec }
6058b464eb8Smec 
6068b464eb8Smec /*
6078b464eb8Smec  * The guts of the PHYSMEM_DESTROY ioctl.
6088b464eb8Smec  * The cookie passed in will provide all of the information needed to
6098b464eb8Smec  * free up the address space and physical memory associated with the
6108b464eb8Smec  * corresponding PHSYMEM_SETUP ioctl.
6118b464eb8Smec  * Returns 0 on success with the following error codes on failure:
6128b464eb8Smec  *	EINVAL - The cookie supplied is not valid.
6138b464eb8Smec  */
6148b464eb8Smec int
6158b464eb8Smec physmem_destroy_addrs(uint64_t p_cookie)
6168b464eb8Smec {
6178b464eb8Smec 	struct as *as = curproc->p_as;
6188b464eb8Smec 	size_t len;
6198b464eb8Smec 	caddr_t uvaddr;
6208b464eb8Smec 
6218b464eb8Smec 	rw_enter(&pph_rwlock, RW_READER);
6228b464eb8Smec 	if (physmem_validate_cookie(p_cookie) == 0) {
6238b464eb8Smec 		rw_exit(&pph_rwlock);
6248b464eb8Smec 		return (EINVAL);
6258b464eb8Smec 	}
6268b464eb8Smec 
6278b464eb8Smec 	len = ((struct physmem_hash *)(uintptr_t)p_cookie)->ph_seg_len;
6288b464eb8Smec 	uvaddr = ((struct physmem_hash *)(uintptr_t)p_cookie)->ph_base_va;
6298b464eb8Smec 	rw_exit(&pph_rwlock);
6308b464eb8Smec 
6318b464eb8Smec 	(void) as_unmap(as, uvaddr, len);
6328b464eb8Smec 
6338b464eb8Smec 	return (0);
6348b464eb8Smec }
6358b464eb8Smec 
6368b464eb8Smec /*
6378b464eb8Smec  * If the page has been hashed into the physmem vnode, then just look it up
6388b464eb8Smec  * and return it via pl, otherwise return ENOMEM as the map ioctl has not
6398b464eb8Smec  * succeeded on the given page.
6408b464eb8Smec  */
6418b464eb8Smec /*ARGSUSED*/
6428b464eb8Smec static int
6438b464eb8Smec physmem_getpage(struct vnode *vp, offset_t off, size_t len, uint_t *protp,
6448b464eb8Smec     page_t *pl[], size_t plsz, struct seg *seg, caddr_t addr, enum seg_rw rw,
645da6c28aaSamw     struct cred *cr, caller_context_t *ct)
6468b464eb8Smec {
6478b464eb8Smec 	page_t *pp;
6488b464eb8Smec 
6498b464eb8Smec 	ASSERT(len == PAGESIZE);
650*dc32d872SJosef 'Jeff' Sipek 	ASSERT(AS_READ_HELD(seg->s_as));
6518b464eb8Smec 
6528b464eb8Smec 	/*
6538b464eb8Smec 	 * If the page is in the hash, then we successfully claimed this
6548b464eb8Smec 	 * page earlier, so return it to the caller.
6558b464eb8Smec 	 */
6568b464eb8Smec 	pp = page_lookup(vp, off, SE_SHARED);
6578b464eb8Smec 	if (pp != NULL) {
6588b464eb8Smec 		pl[0] = pp;
6598b464eb8Smec 		pl[1] = NULL;
6608b464eb8Smec 		*protp = PROT_ALL;
6618b464eb8Smec 		return (0);
6628b464eb8Smec 	}
6638b464eb8Smec 	return (ENOMEM);
6648b464eb8Smec }
6658b464eb8Smec 
6668b464eb8Smec /*
6678b464eb8Smec  * We can not allow a process mapping /dev/physmem pages to fork as there can
6688b464eb8Smec  * only be a single mapping to a /dev/physmem page at a given time.  Thus, the
6698b464eb8Smec  * return of EINVAL when we are not working on our own address space.
6708b464eb8Smec  * Otherwise we return zero as this function is required for normal operation.
6718b464eb8Smec  */
6728b464eb8Smec /*ARGSUSED*/
6738b464eb8Smec static int
6748b464eb8Smec physmem_addmap(struct vnode *vp, offset_t off, struct as *as,
6758b464eb8Smec     caddr_t addr, size_t len, uchar_t prot, uchar_t maxprot, uint_t flags,
676da6c28aaSamw     struct cred *cred, caller_context_t *ct)
6778b464eb8Smec {
6788b464eb8Smec 	if (curproc->p_as != as) {
6798b464eb8Smec 		return (EINVAL);
6808b464eb8Smec 	}
6818b464eb8Smec 	return (0);
6828b464eb8Smec }
6838b464eb8Smec 
6848b464eb8Smec /* Will always get called for removing a whole segment. */
6858b464eb8Smec /*ARGSUSED*/
6868b464eb8Smec static int
6878b464eb8Smec physmem_delmap(struct vnode *vp, offset_t off, struct as *as,
6888b464eb8Smec     caddr_t addr, size_t len, uint_t prot, uint_t maxprot, uint_t flags,
689da6c28aaSamw     struct cred *cred, caller_context_t *ct)
6908b464eb8Smec {
6918b464eb8Smec 	/*
6928b464eb8Smec 	 * Release our hold on the vnode so that the final VN_RELE will
6938b464eb8Smec 	 * call physmem_inactive to clean things up.
6948b464eb8Smec 	 */
6958b464eb8Smec 	VN_RELE(vp);
6968b464eb8Smec 
6978b464eb8Smec 	return (0);
6988b464eb8Smec }
6998b464eb8Smec 
7008b464eb8Smec /*
7018b464eb8Smec  * Clean up all the pages belonging to this vnode and then free it.
7028b464eb8Smec  */
7038b464eb8Smec /*ARGSUSED*/
7048b464eb8Smec static void
705da6c28aaSamw physmem_inactive(vnode_t *vp, cred_t *crp, caller_context_t *ct)
7068b464eb8Smec {
7078b464eb8Smec 	page_t *pp;
7088b464eb8Smec 
7098b464eb8Smec 	/*
7108b464eb8Smec 	 * Remove the vnode from the hash now, to prevent asynchronous
7118b464eb8Smec 	 * attempts to map into this vnode.  This avoids a deadlock
7128b464eb8Smec 	 * where two threads try to get into this logic at the same
7138b464eb8Smec 	 * time and try to map the pages they are destroying into the
7148b464eb8Smec 	 * other's address space.
7158b464eb8Smec 	 * If it's not in the hash, just free it.
7168b464eb8Smec 	 */
7178b464eb8Smec 	if (physmem_remove_vnode_hash(vp) == 0) {
7188b464eb8Smec 		ASSERT(vp->v_pages == NULL);
7198b464eb8Smec 		vn_free(vp);
7208b464eb8Smec 		physmem_remove_hash_proc();
7218b464eb8Smec 		mutex_enter(&physmem_mutex);
7228b464eb8Smec 		physmem_vnodecnt--;
7238b464eb8Smec 		mutex_exit(&physmem_mutex);
7248b464eb8Smec 		return;
7258b464eb8Smec 	}
7268b464eb8Smec 
7278b464eb8Smec 	/*
7288b464eb8Smec 	 * At this point in time, no other logic can be adding or removing
7298b464eb8Smec 	 * pages from the vnode, otherwise the v_pages list could be inaccurate.
7308b464eb8Smec 	 */
7318b464eb8Smec 
7328b464eb8Smec 	while ((pp = vp->v_pages) != NULL) {
7338b464eb8Smec 		page_t *rpp;
7348b464eb8Smec 		if (page_tryupgrade(pp)) {
7358b464eb8Smec 			/*
7368b464eb8Smec 			 * set lckcnt for page_destroy to do availrmem
7378b464eb8Smec 			 * accounting
7388b464eb8Smec 			 */
7398b464eb8Smec 			pp->p_lckcnt = 1;
7408b464eb8Smec 			page_destroy(pp, 0);
7418b464eb8Smec 		} else {
7428b464eb8Smec 			/* failure to lock should be transient */
7438b464eb8Smec 			rpp = page_lookup(vp, ptob(pp->p_pagenum), SE_SHARED);
7448b464eb8Smec 			if (rpp != pp) {
7458b464eb8Smec 				page_unlock(rpp);
7468b464eb8Smec 				continue;
7478b464eb8Smec 			}
7488b464eb8Smec 			page_unlock(pp);
7498b464eb8Smec 		}
7508b464eb8Smec 	}
7518b464eb8Smec 	vn_free(vp);
7528b464eb8Smec 	physmem_remove_hash_proc();
7538b464eb8Smec 	mutex_enter(&physmem_mutex);
7548b464eb8Smec 	physmem_vnodecnt--;
7558b464eb8Smec 	mutex_exit(&physmem_mutex);
7568b464eb8Smec }
7578b464eb8Smec 
7588b464eb8Smec /*ARGSUSED*/
7598b464eb8Smec static int
7608b464eb8Smec physmem_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
7618b464eb8Smec     int *rvalp)
7628b464eb8Smec {
7638b464eb8Smec 	int ret;
7648b464eb8Smec 
7658b464eb8Smec 	switch (cmd) {
7668b464eb8Smec 	case PHYSMEM_SETUP:
7678b464eb8Smec 		{
7688b464eb8Smec 			struct physmem_setup_param psp;
7698b464eb8Smec 			if (ddi_copyin((void *)arg, &psp,
7708b464eb8Smec 			    sizeof (struct physmem_setup_param), 0))
7718b464eb8Smec 				return (EFAULT);
7728b464eb8Smec 			ret = physmem_setup_addrs(&psp);
7738b464eb8Smec 			if (ddi_copyout(&psp, (void *)arg, sizeof (psp), 0))
7748b464eb8Smec 				return (EFAULT);
7758b464eb8Smec 		}
7768b464eb8Smec 		break;
7778b464eb8Smec 	case PHYSMEM_MAP:
7788b464eb8Smec 		{
7798b464eb8Smec 			struct physmem_map_param pmp;
7808b464eb8Smec 			if (ddi_copyin((void *)arg, &pmp,
7818b464eb8Smec 			    sizeof (struct physmem_map_param), 0))
7828b464eb8Smec 				return (EFAULT);
7838b464eb8Smec 			ret = physmem_map_addrs(&pmp);
7848b464eb8Smec 			if (ddi_copyout(&pmp, (void *)arg, sizeof (pmp), 0))
7858b464eb8Smec 				return (EFAULT);
7868b464eb8Smec 		}
7878b464eb8Smec 		break;
7888b464eb8Smec 	case PHYSMEM_DESTROY:
7898b464eb8Smec 		{
7908b464eb8Smec 			uint64_t cookie;
7918b464eb8Smec 			if (ddi_copyin((void *)arg, &cookie,
7928b464eb8Smec 			    sizeof (uint64_t), 0))
7938b464eb8Smec 				return (EFAULT);
7948b464eb8Smec 			ret = physmem_destroy_addrs(cookie);
7958b464eb8Smec 		}
7968b464eb8Smec 		break;
7978b464eb8Smec 	default:
7988b464eb8Smec 		return (ENOTSUP);
7998b464eb8Smec 	}
8008b464eb8Smec 	return (ret);
8018b464eb8Smec }
8028b464eb8Smec 
8038b464eb8Smec /*ARGSUSED*/
8048b464eb8Smec static int
8058b464eb8Smec physmem_open(dev_t *devp, int flag, int otyp, cred_t *credp)
8068b464eb8Smec {
8078b464eb8Smec 	int ret;
8088b464eb8Smec 	static int msg_printed = 0;
8098b464eb8Smec 
8108b464eb8Smec 	if ((flag & (FWRITE | FREAD)) != (FWRITE | FREAD)) {
8118b464eb8Smec 		return (EINVAL);
8128b464eb8Smec 	}
8138b464eb8Smec 
8148b464eb8Smec 	/* need to make sure we have the right privileges */
8158b464eb8Smec 	if ((ret = secpolicy_resource(credp)) != 0)
8168b464eb8Smec 		return (ret);
8178b464eb8Smec 	if ((ret = secpolicy_lock_memory(credp)) != 0)
8188b464eb8Smec 		return (ret);
8198b464eb8Smec 
8208b464eb8Smec 	if (msg_printed == 0) {
8218b464eb8Smec 		cmn_err(CE_NOTE, "!driver has been opened. This driver may "
8228b464eb8Smec 		    "take out long term locks on pages which may impact "
8238b464eb8Smec 		    "dynamic reconfiguration events");
8248b464eb8Smec 		msg_printed = 1;
8258b464eb8Smec 	}
8268b464eb8Smec 
8278b464eb8Smec 	return (0);
8288b464eb8Smec }
8298b464eb8Smec 
8308b464eb8Smec /*ARGSUSED*/
8318b464eb8Smec static int
8328b464eb8Smec physmem_close(dev_t dev, int flag, int otyp, cred_t *credp)
8338b464eb8Smec {
8348b464eb8Smec 	return (0);
8358b464eb8Smec }
8368b464eb8Smec 
8378b464eb8Smec /*ARGSUSED*/
8388b464eb8Smec static int
8398b464eb8Smec physmem_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
8408b464eb8Smec     void *arg, void **resultp)
8418b464eb8Smec {
8428b464eb8Smec 	switch (infocmd) {
8438b464eb8Smec 	case DDI_INFO_DEVT2DEVINFO:
8448b464eb8Smec 		*resultp = physmem_dip;
8458b464eb8Smec 		return (DDI_SUCCESS);
8468b464eb8Smec 
8478b464eb8Smec 	case DDI_INFO_DEVT2INSTANCE:
8488b464eb8Smec 		*resultp = (void *)(ulong_t)getminor((dev_t)arg);
8498b464eb8Smec 		return (DDI_SUCCESS);
8508b464eb8Smec 
8518b464eb8Smec 	default:
8528b464eb8Smec 		return (DDI_FAILURE);
8538b464eb8Smec 	}
8548b464eb8Smec }
8558b464eb8Smec 
8568b464eb8Smec static int
8578b464eb8Smec physmem_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
8588b464eb8Smec {
8598b464eb8Smec 	int i;
8608b464eb8Smec 
8618b464eb8Smec 	if (cmd == DDI_RESUME) {
8628b464eb8Smec 		return (DDI_SUCCESS);
8638b464eb8Smec 	}
8648b464eb8Smec 
8658b464eb8Smec 	if (cmd != DDI_ATTACH)
8668b464eb8Smec 		return (DDI_FAILURE);
8678b464eb8Smec 
8688b464eb8Smec 	if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
8698b464eb8Smec 	    ddi_get_instance(dip), DDI_PSEUDO, 0) != DDI_SUCCESS)
8708b464eb8Smec 		return (DDI_FAILURE);
8718b464eb8Smec 
8728b464eb8Smec 	physmem_dip = dip;
8738b464eb8Smec 
8748b464eb8Smec 	/* Initialize driver specific data */
8758b464eb8Smec 	if (physmem_setup_vnops()) {
8768b464eb8Smec 		ddi_remove_minor_node(dip, ddi_get_name(dip));
8778b464eb8Smec 		return (DDI_FAILURE);
8788b464eb8Smec 	}
8798b464eb8Smec 
8808b464eb8Smec 	for (i = 0; i < PPH_SIZE; i++)
8818b464eb8Smec 		pph[i] = NULL;
8828b464eb8Smec 
8838b464eb8Smec 	page_capture_register_callback(PC_PHYSMEM, 10000,
8848b464eb8Smec 	    map_page_proc);
8858b464eb8Smec 
8868b464eb8Smec 	return (DDI_SUCCESS);
8878b464eb8Smec }
8888b464eb8Smec 
8898b464eb8Smec static int
8908b464eb8Smec physmem_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
8918b464eb8Smec {
8928b464eb8Smec 	int ret = DDI_SUCCESS;
8938b464eb8Smec 
8948b464eb8Smec 	if (cmd == DDI_SUSPEND) {
8958b464eb8Smec 		return (DDI_SUCCESS);
8968b464eb8Smec 	}
8978b464eb8Smec 
8988b464eb8Smec 	if (cmd != DDI_DETACH)
8998b464eb8Smec 		return (DDI_FAILURE);
9008b464eb8Smec 
9018b464eb8Smec 	ASSERT(physmem_dip == dip);
9028b464eb8Smec 
9038b464eb8Smec 	mutex_enter(&physmem_mutex);
9048b464eb8Smec 	if (physmem_vnodecnt == 0) {
9058b464eb8Smec 		if (physmem_vnodeops != NULL) {
9068b464eb8Smec 			vn_freevnodeops(physmem_vnodeops);
9078b464eb8Smec 			physmem_vnodeops = NULL;
9088b464eb8Smec 			page_capture_unregister_callback(PC_PHYSMEM);
9098b464eb8Smec 		}
9108b464eb8Smec 	} else {
9118b464eb8Smec 		ret = EBUSY;
9128b464eb8Smec 	}
9138b464eb8Smec 	mutex_exit(&physmem_mutex);
9148b464eb8Smec 	if (ret == DDI_SUCCESS)
9158b464eb8Smec 		ddi_remove_minor_node(dip, ddi_get_name(dip));
9168b464eb8Smec 	return (ret);
9178b464eb8Smec }
9188b464eb8Smec 
9198b464eb8Smec static struct cb_ops physmem_cb_ops = {
9208b464eb8Smec 	physmem_open,	/* open */
9218b464eb8Smec 	physmem_close,	/* close */
9228b464eb8Smec 	nodev,		/* strategy */
9238b464eb8Smec 	nodev,		/* print */
9248b464eb8Smec 	nodev,		/* dump */
9258b464eb8Smec 	nodev,		/* read */
9268b464eb8Smec 	nodev,		/* write */
9278b464eb8Smec 	physmem_ioctl,	/* ioctl */
9288b464eb8Smec 	nodev,		/* devmap */
9298b464eb8Smec 	nodev,		/* mmap */
9308b464eb8Smec 	nodev,		/* segmap */
9318b464eb8Smec 	nochpoll,	/* chpoll */
9328b464eb8Smec 	ddi_prop_op,	/* prop_op */
9338b464eb8Smec 	NULL,		/* cb_str */
9348b464eb8Smec 	D_NEW | D_MP | D_DEVMAP,
9358b464eb8Smec 	CB_REV,
9368b464eb8Smec 	NULL,
9378b464eb8Smec 	NULL
9388b464eb8Smec };
9398b464eb8Smec 
9408b464eb8Smec static struct dev_ops physmem_ops = {
9418b464eb8Smec 	DEVO_REV,
9428b464eb8Smec 	0,
9438b464eb8Smec 	physmem_getinfo,
9448b464eb8Smec 	nulldev,
9458b464eb8Smec 	nulldev,
9468b464eb8Smec 	physmem_attach,
9478b464eb8Smec 	physmem_detach,
9488b464eb8Smec 	nodev,
9498b464eb8Smec 	&physmem_cb_ops,
9508b464eb8Smec 	NULL,
95119397407SSherry Moore 	NULL,
95219397407SSherry Moore 	ddi_quiesce_not_needed,		/* quiesce */
9538b464eb8Smec };
9548b464eb8Smec 
9558b464eb8Smec static struct modldrv modldrv = {
9568b464eb8Smec 	&mod_driverops,
95719397407SSherry Moore 	"physmem driver",
9588b464eb8Smec 	&physmem_ops
9598b464eb8Smec };
9608b464eb8Smec 
9618b464eb8Smec static struct modlinkage modlinkage = {
9628b464eb8Smec 	MODREV_1,
9638b464eb8Smec 	&modldrv,
9648b464eb8Smec 	NULL
9658b464eb8Smec };
9668b464eb8Smec 
9678b464eb8Smec int
9688b464eb8Smec _init(void)
9698b464eb8Smec {
9708b464eb8Smec 	return (mod_install(&modlinkage));
9718b464eb8Smec }
9728b464eb8Smec 
9738b464eb8Smec int
9748b464eb8Smec _info(struct modinfo *modinfop)
9758b464eb8Smec {
9768b464eb8Smec 	return (mod_info(&modlinkage, modinfop));
9778b464eb8Smec }
9788b464eb8Smec 
9798b464eb8Smec int
9808b464eb8Smec _fini(void)
9818b464eb8Smec {
9828b464eb8Smec 	return (mod_remove(&modlinkage));
9838b464eb8Smec }
984