blob: ed4644088bd4866294eade759c6d55b0d0b104b4 [file] [log] [blame]
/*
* SN Platform FetchOp Support
*
* This driver exports the SN fetchop facility to user processes.
* Fetchops are atomic memory operations that are implemented in the
* memory controller on SGI SN hardware.
*/
/*
* Copyright (C) 1999,2001-2003 Silicon Graphics, Inc. All rights
* reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it would be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Further, this software is distributed without any warranty that it is
* free of the rightful claim of any third person regarding infringement
* or the like. Any license provided herein, whether implied or
* otherwise, applies only to this software file. Patent licenses, if
* any, provided herein do not apply to combinations of this program with
* other software, or any other product whatsoever.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston MA
* 02111-1307, USA.
*
* Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
* Mountain View, CA 94043, or:
*
* http://www.sgi.com
*
* For further information regarding this notice, see:
*
* http://oss.sgi.com/projects/GenInfo/NoticeExplan
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/miscdevice.h>
#include <linux/spinlock.h>
#include <linux/mm.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include <linux/bitops.h>
#include <linux/efi.h>
#include <asm/system.h>
#include <asm/pgtable.h>
#include <asm/machvec.h>
#include <asm/sn/sgi.h>
#include <asm/sn/addrs.h>
#include <asm/sn/arch.h>
#include <asm/sn/fetchop.h>
#include <asm/sn/sn_cpuid.h>
#define DRIVER_ID_STR "SGI Fetchop Device Driver"
#define REVISION "1.03"
#define MSPEC_TO_NID(maddr) nasid_to_cnodeid(NASID_GET(maddr))
static int fetchop_mmap(struct file *file, struct vm_area_struct *vma);
static void fetchop_open(struct vm_area_struct *vma);
static void fetchop_close(struct vm_area_struct *vma);
static struct file_operations fetchop_fops = {
owner: THIS_MODULE,
mmap: fetchop_mmap,
};
static struct miscdevice fetchop_miscdev = {
MISC_DYNAMIC_MINOR,
"fetchop",
&fetchop_fops
};
static struct vm_operations_struct fetchop_vm_ops = {
open: fetchop_open,
close: fetchop_close,
};
/*
* There is one of these structs per node. It is used to manage the fetchop
* space that is available on the node. Current assumption is that there is
* only 1 fetchop block of memory per node.
*/
struct node_fetchops {
long maddr; /* MSPEC address of start of fetchops. */
int count; /* Total number of fetchop pages. */
atomic_t free; /* Number of pages currently free. */
unsigned long bits[1]; /* Bitmap for managing pages. */
};
/*
* One of these structures is allocated when a fetchop region is mmaped. The
* structure is pointed to by the vma->vm_private_data field in the vma struct.
* This structure is used to record the addresses of the fetchop pages.
*/
struct vma_data {
int count; /* Number of pages allocated. */
atomic_t refcnt; /* Number of vmas sharing the data. */
unsigned long maddr[1]; /* Array of MSPEC addresses. */
};
/*
* Fetchop statistics.
*/
struct fetchop_stats {
unsigned long map_count; /* Number of active mmap's */
unsigned long pages_in_use; /* Number of fetchop pages in use */
unsigned long pages_total; /* Total number of fetchop pages */
};
static struct fetchop_stats fetchop_stats;
static struct node_fetchops *node_fetchops[MAX_COMPACT_NODES];
static spinlock_t fetchop_lock = SPIN_LOCK_UNLOCKED;
/*
* NOTE: This is included here simply because the kernel doesn't have
* a generally acceptable UC memory allocator. See PV: 896479 for
* more details. --cw
*
* efi_memmap_walk_uc
*
* This function walks the EFI memory map and calls 'callback' once
* for each EFI memory descriptor that has memory that marked as only
* EFI_MEMORY_UC.
*/
static void
efi_memmap_walk_uc (efi_freemem_callback_t callback, void *arg)
{
void *efi_map_start, *efi_map_end, *p;
efi_memory_desc_t *md;
u64 efi_desc_size, start, end;
efi_map_start = __va(ia64_boot_param->efi_memmap);
efi_map_end = efi_map_start + ia64_boot_param->efi_memmap_size;
efi_desc_size = ia64_boot_param->efi_memdesc_size;
for (p = efi_map_start; p < efi_map_end; p += efi_desc_size) {
md = p;
if (md->attribute == EFI_MEMORY_UC) {
start = PAGE_ALIGN(md->phys_addr);
end = PAGE_ALIGN((md->phys_addr+(md->num_pages << EFI_PAGE_SHIFT)) & PAGE_MASK);
if ((*callback)(start, end, arg) < 0)
return;
}
}
}
/*
* fetchop_initialize_page
*
* Initial a page that is about to be used for fetchops.
* All fetchop variables in the page are set to 0.
*
*/
static void
fetchop_initialize_page(unsigned long maddr)
{
unsigned long p, pe;
for (p=FETCHOP_KADDR_TO_MSPEC_ADDR(maddr), pe=p+PAGE_SIZE; p<pe; p+=FETCHOP_VAR_SIZE)
FETCHOP_STORE_OP(p,FETCHOP_STORE, 0);
}
/*
* fetchop_alloc_page
*
* Allocate 1 fetchop page. Allocates on the requested node. If no
* fetchops are available on the requested node, roundrobin starting
* with higher nodes,
*/
static unsigned long
fetchop_alloc_page(int nid)
{
int i, bit;
struct node_fetchops *fops;
unsigned long maddr;
if (nid < 0 || nid >= numnodes)
nid = numa_node_id();
for (i=0; i<numnodes; i++) {
fops = node_fetchops[nid];
while (fops && (bit = find_first_zero_bit(fops->bits, fops->count)) < fops->count) {
if (test_and_set_bit(bit, fops->bits) == 0) {
atomic_dec(&node_fetchops[nid]->free);
maddr = fops->maddr + (bit<<PAGE_SHIFT);
fetchop_initialize_page(maddr);
return maddr;
}
}
nid = (nid+1 < numnodes) ? nid+1 : 0;
}
return 0;
}
/*
* fetchop_free_pages
*
* Free all fetchop pages that are linked to a vma struct.
*/
static void
fetchop_free_page(unsigned long maddr)
{
int nid, bit;
nid = MSPEC_TO_NID(maddr);
bit = (maddr - node_fetchops[nid]->maddr) >> PAGE_SHIFT;
clear_bit(bit, node_fetchops[nid]->bits);
atomic_inc(&node_fetchops[nid]->free);
}
static void
fetchop_free_pages(struct vma_data *vdata)
{
int i;
for (i=0; i<vdata->count; i++)
fetchop_free_page(vdata->maddr[i]);
}
/*
* fetchop_update_stats
*
* Update statistics of the number of fetchop mappings & pages.
* If creating a new mapping, ensure that we don't exceed the maximum allowed
* number of fetchop pages.
*/
static int
fetchop_update_stats(int mmap, long count)
{
int ret = 0;
spin_lock(&fetchop_lock);
if (count > 0 && fetchop_stats.pages_in_use + count > fetchop_stats.pages_total) {
ret = -1;
} else {
fetchop_stats.map_count += mmap;
fetchop_stats.pages_in_use += count;
}
spin_unlock(&fetchop_lock);
return ret;
}
/*
* fetchop_mmap
*
* Called when mmaping the device. Creates fetchop pages and map them
* to user space.
*/
static int
fetchop_mmap(struct file *file, struct vm_area_struct *vma)
{
unsigned long vm_start;
unsigned long maddr;
int pages;
struct vma_data *vdata;
if (vma->vm_pgoff != 0)
return -EINVAL;
if ((vma->vm_flags&VM_WRITE) == 0)
return -EPERM;
pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
if (fetchop_update_stats(1, pages) < 0)
return -ENOSPC;
if (!(vdata=vmalloc(sizeof(struct vma_data)+(pages-1)*sizeof(long)))) {
fetchop_update_stats(-1, -pages);
return -ENOMEM;
}
vdata->count = 0;
vdata->refcnt = ATOMIC_INIT(1);
vma->vm_private_data = vdata;
vma->vm_flags |= (VM_IO | VM_SHM | VM_LOCKED | VM_NONCACHED);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
vma->vm_ops = &fetchop_vm_ops;
vm_start = vma->vm_start;
while (vm_start < vma->vm_end) {
maddr = fetchop_alloc_page(numa_node_id());
if (maddr == 0)
BUG();
vdata->maddr[vdata->count++] = maddr;
if (remap_page_range(vm_start, __pa(maddr), PAGE_SIZE, vma->vm_page_prot)) {
fetchop_free_pages(vma->vm_private_data);
vfree(vdata);
fetchop_update_stats(-1, -pages);
return -EAGAIN;
}
vm_start += PAGE_SIZE;
}
return 0;
}
/*
* fetchop_open
*
* Called when a device mapping is created by a means other than mmap
* (via fork, etc.). Increments the reference count on the underlying
* fetchop data so it is not freed prematurely.
*/
static void
fetchop_open(struct vm_area_struct *vma)
{
struct vma_data *vdata;
vdata = vma->vm_private_data;
if (vdata && vdata->count) {
atomic_inc(&vdata->refcnt);
}
}
/*
* fetchop_close
*
* Called when unmapping a device mapping. Frees all fetchop pages
* belonging to the vma.
*/
static void
fetchop_close(struct vm_area_struct *vma)
{
struct vma_data *vdata;
vdata = vma->vm_private_data;
if (vdata && vdata->count && !atomic_dec(&vdata->refcnt)) {
fetchop_free_pages(vdata);
fetchop_update_stats(-1, -vdata->count);
vfree(vdata);
}
}
#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *proc_fetchop;
/*
* fetchop_read_proc
*
* Implements /proc/fetchop. Return statistics about fetchops.
*/
static int
fetchop_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{
struct node_fetchops *fops;
int len = 0, nid;
len += sprintf(page + len, "mappings : %lu\n", fetchop_stats.map_count);
len += sprintf(page + len, "current fetchop pages : %lu\n", fetchop_stats.pages_in_use);
len += sprintf(page + len, "maximum fetchop pages : %lu\n", fetchop_stats.pages_total);
len += sprintf(page + len, "%4s %7s %7s\n", "node", "total", "free");
for (nid = 0; nid < numnodes; nid++) {
fops = node_fetchops[nid];
len += sprintf(page + len, "%4d %7d %7d\n", nid, fops ? fops->count : 0, fops ? atomic_read(&fops->free) : 0);
}
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return len;
}
static int
fetchop_write_proc (struct file *file, const char *userbuf, unsigned long count, void *data)
{
char buf[80];
if (copy_from_user(buf, userbuf, count < sizeof(buf) ? count : sizeof(buf)))
return -EFAULT;
return count;
}
#endif /* CONFIG_PROC_FS */
/*
* fetchop_build_memmap,
*
* Called at boot time to build a map of pages that can be used for
* fetchops.
*/
static int __init
fetchop_build_memmap(unsigned long start, unsigned long end, void *arg)
{
struct node_fetchops *fops;
long count, bytes;
count = (end - start) >> PAGE_SHIFT;
bytes = sizeof(struct node_fetchops) + count/8;
fops = vmalloc(bytes);
memset(fops, 0, bytes);
fops->maddr = FETCHOP_KADDR_TO_MSPEC_ADDR(start);
fops->count = count;
atomic_add(count, &fops->free);
fetchop_stats.pages_total += count;
node_fetchops[MSPEC_TO_NID(start)] = fops;
sn_flush_all_caches((long)__va(start), end - start);
return 0;
}
/*
* fetchop_init
*
* Called at boot time to initialize the fetchop facility.
*/
static int __init
fetchop_init(void)
{
int ret;
devfs_handle_t hnd;
if (!ia64_platform_is("sn2"))
return -ENODEV;
#ifdef CONFIG_DEVFS_FS
if (!devfs_register(NULL, FETCHOP_BASENAME, DEVFS_FL_AUTO_DEVNUM,
0, 0, S_IFCHR | S_IRUGO | S_IWUGO, &fetchop_fops, NULL)) {
printk(KERN_ERR "%s: failed to register device\n", DRIVER_ID_STR);
return -ENODEV;
}
#endif
if ((ret = misc_register(&fetchop_miscdev))) {
printk(KERN_ERR "%s: failed to register device\n", DRIVER_ID_STR);
return ret;
}
printk(KERN_DEBUG "%s: registered misc-device with minor %d\n", DRIVER_ID_STR, fetchop_miscdev.minor);
if ((proc_fetchop = create_proc_entry(FETCHOP_BASENAME, 0644, NULL)) == NULL) {
printk(KERN_ERR "%s: unable to create proc entry", DRIVER_ID_STR);
devfs_unregister(hnd);
return -EINVAL;
}
#ifdef CONFIG_PROC_FS
if ((proc_fetchop = create_proc_entry(FETCHOP_BASENAME, 0644, NULL)) == NULL) {
printk(KERN_ERR "%s: unable to create proc entry", DRIVER_ID_STR);
devfs_unregister(hnd);
return -EINVAL;
}
proc_fetchop->read_proc = fetchop_read_proc;
proc_fetchop->write_proc = fetchop_write_proc;
#endif /* CONFIG_PROC_FS */
efi_memmap_walk_uc(fetchop_build_memmap, 0);
printk(KERN_INFO "%s: v%s\n", DRIVER_ID_STR, REVISION);
return 0;
}
/*-----------------------------------------------------------------------------
* KERNEL APIs
* Note: right now, these APIs return a full page of fetchops. If these
* interfaces are used often for tasks which do not require a full page of
* fetchops, new APIs should be added to suballocate out of a single page.
*/
unsigned long
fetchop_kalloc_page(int nid)
{
if (fetchop_update_stats(1, 1) < 0)
return 0;
return fetchop_alloc_page(nid);
}
EXPORT_SYMBOL(fetchop_kalloc_page);
void
fetchop_kfree_page(unsigned long maddr)
{
fetchop_free_page(maddr);
fetchop_update_stats(-1, -1);
}
EXPORT_SYMBOL(fetchop_kfree_page);
module_init(fetchop_init);
MODULE_AUTHOR("Silicon Graphics Inc.");
MODULE_DESCRIPTION("Driver for SGI SN 'fetchop' atomic memory operations");
MODULE_LICENSE("GPL");