/* * SN Platform system controller communication support * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2004, 2006 Silicon Graphics, Inc. All rights reserved. */ /* * System controller communication driver * * This driver allows a user process to communicate with the system * controller (a.k.a. "IRouter") network in an SGI SN system. */ #include <linux/interrupt.h> #include <linux/sched.h> #include <linux/device.h> #include <linux/poll.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/mutex.h> #include <asm/sn/io.h> #include <asm/sn/sn_sal.h> #include <asm/sn/module.h> #include <asm/sn/geo.h> #include <asm/sn/nodepda.h> #include "snsc.h" #define SYSCTL_BASENAME "snsc" #define SCDRV_BUFSZ 2048 #define SCDRV_TIMEOUT 1000 static DEFINE_MUTEX(scdrv_mutex); static irqreturn_t scdrv_interrupt(int irq, void *subch_data) { struct subch_data_s *sd = subch_data; unsigned long flags; int status; spin_lock_irqsave(&sd->sd_rlock, flags); spin_lock(&sd->sd_wlock); status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch); if (status > 0) { if (status & SAL_IROUTER_INTR_RECV) { wake_up(&sd->sd_rq); } if (status & SAL_IROUTER_INTR_XMIT) { ia64_sn_irtr_intr_disable (sd->sd_nasid, sd->sd_subch, SAL_IROUTER_INTR_XMIT); wake_up(&sd->sd_wq); } } spin_unlock(&sd->sd_wlock); spin_unlock_irqrestore(&sd->sd_rlock, flags); return IRQ_HANDLED; } /* * scdrv_open * * Reserve a subchannel for system controller communication. */ static int scdrv_open(struct inode *inode, struct file *file) { struct sysctl_data_s *scd; struct subch_data_s *sd; int rv; /* look up device info for this device file */ scd = container_of(inode->i_cdev, struct sysctl_data_s, scd_cdev); /* allocate memory for subchannel data */ sd = kzalloc(sizeof (struct subch_data_s), GFP_KERNEL); if (sd == NULL) { printk("%s: couldn't allocate subchannel data\n", __func__); return -ENOMEM; } /* initialize subch_data_s fields */ sd->sd_nasid = scd->scd_nasid; sd->sd_subch = ia64_sn_irtr_open(scd->scd_nasid); if (sd->sd_subch < 0) { kfree(sd); printk("%s: couldn't allocate subchannel\n", __func__); return -EBUSY; } spin_lock_init(&sd->sd_rlock); spin_lock_init(&sd->sd_wlock); init_waitqueue_head(&sd->sd_rq); init_waitqueue_head(&sd->sd_wq); sema_init(&sd->sd_rbs, 1); sema_init(&sd->sd_wbs, 1); file->private_data = sd; /* hook this subchannel up to the system controller interrupt */ mutex_lock(&scdrv_mutex); rv = request_irq(SGI_UART_VECTOR, scdrv_interrupt, IRQF_SHARED | IRQF_DISABLED, SYSCTL_BASENAME, sd); if (rv) { ia64_sn_irtr_close(sd->sd_nasid, sd->sd_subch); kfree(sd); printk("%s: irq request failed (%d)\n", __func__, rv); mutex_unlock(&scdrv_mutex); return -EBUSY; } mutex_unlock(&scdrv_mutex); return 0; } /* * scdrv_release * * Release a previously-reserved subchannel. */ static int scdrv_release(struct inode *inode, struct file *file) { struct subch_data_s *sd = (struct subch_data_s *) file->private_data; int rv; /* free the interrupt */ free_irq(SGI_UART_VECTOR, sd); /* ask SAL to close the subchannel */ rv = ia64_sn_irtr_close(sd->sd_nasid, sd->sd_subch); kfree(sd); return rv; } /* * scdrv_read * * Called to read bytes from the open IRouter pipe. * */ static inline int read_status_check(struct subch_data_s *sd, int *len) { return ia64_sn_irtr_recv(sd->sd_nasid, sd->sd_subch, sd->sd_rb, len); } static ssize_t scdrv_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) { int status; int len; unsigned long flags; struct subch_data_s *sd = (struct subch_data_s *) file->private_data; /* try to get control of the read buffer */ if (down_trylock(&sd->sd_rbs)) { /* somebody else has it now; * if we're non-blocking, then exit... */ if (file->f_flags & O_NONBLOCK) { return -EAGAIN; } /* ...or if we want to block, then do so here */ if (down_interruptible(&sd->sd_rbs)) { /* something went wrong with wait */ return -ERESTARTSYS; } } /* anything to read? */ len = CHUNKSIZE; spin_lock_irqsave(&sd->sd_rlock, flags); status = read_status_check(sd, &len); /* if not, and we're blocking I/O, loop */ while (status < 0) { DECLARE_WAITQUEUE(wait, current); if (file->f_flags & O_NONBLOCK) { spin_unlock_irqrestore(&sd->sd_rlock, flags); up(&sd->sd_rbs); return -EAGAIN; } len = CHUNKSIZE; set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&sd->sd_rq, &wait); spin_unlock_irqrestore(&sd->sd_rlock, flags); schedule_timeout(SCDRV_TIMEOUT); remove_wait_queue(&sd->sd_rq, &wait); if (signal_pending(current)) { /* wait was interrupted */ up(&sd->sd_rbs); return -ERESTARTSYS; } spin_lock_irqsave(&sd->sd_rlock, flags); status = read_status_check(sd, &len); } spin_unlock_irqrestore(&sd->sd_rlock, flags); if (len > 0) { /* we read something in the last read_status_check(); copy * it out to user space */ if (count < len) { pr_debug("%s: only accepting %d of %d bytes\n", __func__, (int) count, len); } len = min((int) count, len); if (copy_to_user(buf, sd->sd_rb, len)) len = -EFAULT; } /* release the read buffer and wake anyone who might be * waiting for it */ up(&sd->sd_rbs); /* return the number of characters read in */ return len; } /* * scdrv_write * * Writes a chunk of an IRouter packet (or other system controller data) * to the system controller. * */ static inline int write_status_check(struct subch_data_s *sd, int count) { return ia64_sn_irtr_send(sd->sd_nasid, sd->sd_subch, sd->sd_wb, count); } static ssize_t scdrv_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos) { unsigned long flags; int status; struct subch_data_s *sd = (struct subch_data_s *) file->private_data; /* try to get control of the write buffer */ if (down_trylock(&sd->sd_wbs)) { /* somebody else has it now; * if we're non-blocking, then exit... */ if (file->f_flags & O_NONBLOCK) { return -EAGAIN; } /* ...or if we want to block, then do so here */ if (down_interruptible(&sd->sd_wbs)) { /* something went wrong with wait */ return -ERESTARTSYS; } } count = min((int) count, CHUNKSIZE); if (copy_from_user(sd->sd_wb, buf, count)) { up(&sd->sd_wbs); return -EFAULT; } /* try to send the buffer */ spin_lock_irqsave(&sd->sd_wlock, flags); status = write_status_check(sd, count); /* if we failed, and we want to block, then loop */ while (status <= 0) { DECLARE_WAITQUEUE(wait, current); if (file->f_flags & O_NONBLOCK) { spin_unlock(&sd->sd_wlock); up(&sd->sd_wbs); return -EAGAIN; } set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&sd->sd_wq, &wait); spin_unlock_irqrestore(&sd->sd_wlock, flags); schedule_timeout(SCDRV_TIMEOUT); remove_wait_queue(&sd->sd_wq, &wait); if (signal_pending(current)) { /* wait was interrupted */ up(&sd->sd_wbs); return -ERESTARTSYS; } spin_lock_irqsave(&sd->sd_wlock, flags); status = write_status_check(sd, count); } spin_unlock_irqrestore(&sd->sd_wlock, flags); /* release the write buffer and wake anyone who's waiting for it */ up(&sd->sd_wbs); /* return the number of characters accepted (should be the complete * "chunk" as requested) */ if ((status >= 0) && (status < count)) { pr_debug("Didn't accept the full chunk; %d of %d\n", status, (int) count); } return status; } static unsigned int scdrv_poll(struct file *file, struct poll_table_struct *wait) { unsigned int mask = 0; int status = 0; struct subch_data_s *sd = (struct subch_data_s *) file->private_data; unsigned long flags; poll_wait(file, &sd->sd_rq, wait); poll_wait(file, &sd->sd_wq, wait); spin_lock_irqsave(&sd->sd_rlock, flags); spin_lock(&sd->sd_wlock); status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch); spin_unlock(&sd->sd_wlock); spin_unlock_irqrestore(&sd->sd_rlock, flags); if (status > 0) { if (status & SAL_IROUTER_INTR_RECV) { mask |= POLLIN | POLLRDNORM; } if (status & SAL_IROUTER_INTR_XMIT) { mask |= POLLOUT | POLLWRNORM; } } return mask; } static const struct file_operations scdrv_fops = { .owner = THIS_MODULE, .read = scdrv_read, .write = scdrv_write, .poll = scdrv_poll, .open = scdrv_open, .release = scdrv_release, .llseek = noop_llseek, }; static struct class *snsc_class; /* * scdrv_init * * Called at boot time to initialize the system controller communication * facility. */ int __init scdrv_init(void) { geoid_t geoid; cnodeid_t cnode; char devname[32]; char *devnamep; struct sysctl_data_s *scd; void *salbuf; dev_t first_dev, dev; nasid_t event_nasid; if (!ia64_platform_is("sn2")) return -ENODEV; event_nasid = ia64_sn_get_console_nasid(); if (alloc_chrdev_region(&first_dev, 0, num_cnodes, SYSCTL_BASENAME) < 0) { printk("%s: failed to register SN system controller device\n", __func__); return -ENODEV; } snsc_class = class_create(THIS_MODULE, SYSCTL_BASENAME); for (cnode = 0; cnode < num_cnodes; cnode++) { geoid = cnodeid_get_geoid(cnode); devnamep = devname; format_module_id(devnamep, geo_module(geoid), MODULE_FORMAT_BRIEF); devnamep = devname + strlen(devname); sprintf(devnamep, "^%d#%d", geo_slot(geoid), geo_slab(geoid)); /* allocate sysctl device data */ scd = kzalloc(sizeof (struct sysctl_data_s), GFP_KERNEL); if (!scd) { printk("%s: failed to allocate device info" "for %s/%s\n", __func__, SYSCTL_BASENAME, devname); continue; } /* initialize sysctl device data fields */ scd->scd_nasid = cnodeid_to_nasid(cnode); if (!(salbuf = kmalloc(SCDRV_BUFSZ, GFP_KERNEL))) { printk("%s: failed to allocate driver buffer" "(%s%s)\n", __func__, SYSCTL_BASENAME, devname); kfree(scd); continue; } if (ia64_sn_irtr_init(scd->scd_nasid, salbuf, SCDRV_BUFSZ) < 0) { printk ("%s: failed to initialize SAL for" " system controller communication" " (%s/%s): outdated PROM?\n", __func__, SYSCTL_BASENAME, devname); kfree(scd); kfree(salbuf); continue; } dev = first_dev + cnode; cdev_init(&scd->scd_cdev, &scdrv_fops); if (cdev_add(&scd->scd_cdev, dev, 1)) { printk("%s: failed to register system" " controller device (%s%s)\n", __func__, SYSCTL_BASENAME, devname); kfree(scd); kfree(salbuf); continue; } device_create(snsc_class, NULL, dev, NULL, "%s", devname); ia64_sn_irtr_intr_enable(scd->scd_nasid, 0 /*ignored */ , SAL_IROUTER_INTR_RECV); /* on the console nasid, prepare to receive * system controller environmental events */ if(scd->scd_nasid == event_nasid) { scdrv_event_init(scd); } } return 0; } module_init(scdrv_init);