| /* |
| * |
| * Copyright 1999 Digi International (www.digi.com) |
| * James Puzzo <jamesp at digi dot com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the |
| * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
| * PURPOSE. See the GNU General Public License for more details. |
| * |
| */ |
| |
| /* |
| * |
| * Filename: |
| * |
| * dgrp_dpa_ops.c |
| * |
| * Description: |
| * |
| * Handle the file operations required for the "dpa" devices. |
| * Includes those functions required to register the "dpa" devices |
| * in "/proc". |
| * |
| * Author: |
| * |
| * James A. Puzzo |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/proc_fs.h> |
| #include <linux/tty.h> |
| #include <linux/poll.h> |
| #include <linux/cred.h> |
| #include <linux/sched.h> |
| #include <linux/ratelimit.h> |
| #include <linux/slab.h> |
| #include <asm/unaligned.h> |
| |
| #include "dgrp_common.h" |
| |
| /* File operation declarations */ |
| static int dgrp_dpa_open(struct inode *, struct file *); |
| static int dgrp_dpa_release(struct inode *, struct file *); |
| static ssize_t dgrp_dpa_read(struct file *, char __user *, size_t, loff_t *); |
| static long dgrp_dpa_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg); |
| static unsigned int dgrp_dpa_select(struct file *, struct poll_table_struct *); |
| |
| const struct file_operations dgrp_dpa_ops = { |
| .owner = THIS_MODULE, |
| .read = dgrp_dpa_read, |
| .poll = dgrp_dpa_select, |
| .unlocked_ioctl = dgrp_dpa_ioctl, |
| .open = dgrp_dpa_open, |
| .release = dgrp_dpa_release, |
| }; |
| |
| struct digi_node { |
| uint nd_state; /* Node state: 1 = up, 0 = down. */ |
| uint nd_chan_count; /* Number of channels found */ |
| uint nd_tx_byte; /* Tx data count */ |
| uint nd_rx_byte; /* RX data count */ |
| u8 nd_ps_desc[MAX_DESC_LEN]; /* Description from PS */ |
| }; |
| |
| #define DIGI_GETNODE (('d'<<8) | 249) /* get board info */ |
| |
| |
| struct digi_chan { |
| uint ch_port; /* Port number to get info on */ |
| uint ch_open; /* 1 if open, 0 if not */ |
| uint ch_txcount; /* TX data count */ |
| uint ch_rxcount; /* RX data count */ |
| uint ch_s_brate; /* Realport BRATE */ |
| uint ch_s_estat; /* Realport ELAST */ |
| uint ch_s_cflag; /* Realport CFLAG */ |
| uint ch_s_iflag; /* Realport IFLAG */ |
| uint ch_s_oflag; /* Realport OFLAG */ |
| uint ch_s_xflag; /* Realport XFLAG */ |
| uint ch_s_mstat; /* Realport MLAST */ |
| }; |
| |
| #define DIGI_GETCHAN (('d'<<8) | 248) /* get channel info */ |
| |
| |
| struct digi_vpd { |
| int vpd_len; |
| char vpd_data[VPDSIZE]; |
| }; |
| |
| #define DIGI_GETVPD (('d'<<8) | 246) /* get VPD info */ |
| |
| |
| struct digi_debug { |
| int onoff; |
| int port; |
| }; |
| |
| #define DIGI_SETDEBUG (('d'<<8) | 247) /* set debug info */ |
| |
| |
| /* |
| * dgrp_dpa_open -- open the DPA device for a particular PortServer |
| */ |
| static int dgrp_dpa_open(struct inode *inode, struct file *file) |
| { |
| struct nd_struct *nd; |
| int rtn = 0; |
| |
| rtn = try_module_get(THIS_MODULE); |
| if (!rtn) |
| return -ENXIO; |
| |
| rtn = 0; |
| |
| if (!capable(CAP_SYS_ADMIN)) { |
| rtn = -EPERM; |
| goto done; |
| } |
| |
| /* |
| * Make sure that the "private_data" field hasn't already been used. |
| */ |
| if (file->private_data) { |
| rtn = -EINVAL; |
| goto done; |
| } |
| |
| /* |
| * Get the node pointer, and fail if it doesn't exist. |
| */ |
| nd = PDE_DATA(inode); |
| if (!nd) { |
| rtn = -ENXIO; |
| goto done; |
| } |
| |
| file->private_data = (void *) nd; |
| |
| /* |
| * Allocate the DPA buffer. |
| */ |
| |
| if (nd->nd_dpa_buf) { |
| rtn = -EBUSY; |
| } else { |
| nd->nd_dpa_buf = kmalloc(DPA_MAX, GFP_KERNEL); |
| |
| if (!nd->nd_dpa_buf) { |
| rtn = -ENOMEM; |
| } else { |
| nd->nd_dpa_out = 0; |
| nd->nd_dpa_in = 0; |
| nd->nd_dpa_lbolt = jiffies; |
| } |
| } |
| |
| done: |
| |
| if (rtn) |
| module_put(THIS_MODULE); |
| return rtn; |
| } |
| |
| /* |
| * dgrp_dpa_release -- close the DPA device for a particular PortServer |
| */ |
| static int dgrp_dpa_release(struct inode *inode, struct file *file) |
| { |
| struct nd_struct *nd; |
| u8 *buf; |
| unsigned long lock_flags; |
| |
| /* |
| * Get the node pointer, and quit if it doesn't exist. |
| */ |
| nd = (struct nd_struct *)(file->private_data); |
| if (!nd) |
| goto done; |
| |
| /* |
| * Free the dpa buffer. |
| */ |
| |
| spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags); |
| |
| buf = nd->nd_dpa_buf; |
| |
| nd->nd_dpa_buf = NULL; |
| nd->nd_dpa_out = nd->nd_dpa_in; |
| |
| /* |
| * Wakeup any thread waiting for buffer space. |
| */ |
| |
| if (nd->nd_dpa_flag & DPA_WAIT_SPACE) { |
| nd->nd_dpa_flag &= ~DPA_WAIT_SPACE; |
| wake_up_interruptible(&nd->nd_dpa_wqueue); |
| } |
| |
| spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags); |
| |
| kfree(buf); |
| |
| done: |
| module_put(THIS_MODULE); |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| /* |
| * dgrp_dpa_read |
| * |
| * Copy data from the monitoring buffer to the user, freeing space |
| * in the monitoring buffer for more messages |
| */ |
| static ssize_t dgrp_dpa_read(struct file *file, char __user *buf, size_t count, |
| loff_t *ppos) |
| { |
| struct nd_struct *nd; |
| int n; |
| int r; |
| int offset = 0; |
| int res = 0; |
| ssize_t rtn; |
| unsigned long lock_flags; |
| |
| /* |
| * Get the node pointer, and quit if it doesn't exist. |
| */ |
| nd = (struct nd_struct *)(file->private_data); |
| if (!nd) |
| return -ENXIO; |
| |
| /* |
| * Wait for some data to appear in the buffer. |
| */ |
| |
| spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags); |
| |
| for (;;) { |
| n = (nd->nd_dpa_in - nd->nd_dpa_out) & DPA_MASK; |
| |
| if (n != 0) |
| break; |
| |
| nd->nd_dpa_flag |= DPA_WAIT_DATA; |
| |
| spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags); |
| |
| /* |
| * Go to sleep waiting until the condition becomes true. |
| */ |
| rtn = wait_event_interruptible(nd->nd_dpa_wqueue, |
| ((nd->nd_dpa_flag & DPA_WAIT_DATA) == 0)); |
| |
| if (rtn) |
| return rtn; |
| |
| spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags); |
| } |
| |
| /* |
| * Read whatever is there. |
| */ |
| |
| if (n > count) |
| n = count; |
| |
| res = n; |
| |
| r = DPA_MAX - nd->nd_dpa_out; |
| |
| if (r <= n) { |
| |
| spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags); |
| rtn = copy_to_user((void __user *)buf, |
| nd->nd_dpa_buf + nd->nd_dpa_out, r); |
| spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags); |
| |
| if (rtn) { |
| rtn = -EFAULT; |
| goto done; |
| } |
| |
| nd->nd_dpa_out = 0; |
| n -= r; |
| offset = r; |
| } |
| |
| spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags); |
| rtn = copy_to_user((void __user *)buf + offset, |
| nd->nd_dpa_buf + nd->nd_dpa_out, n); |
| spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags); |
| |
| if (rtn) { |
| rtn = -EFAULT; |
| goto done; |
| } |
| |
| nd->nd_dpa_out += n; |
| |
| *ppos += res; |
| |
| rtn = res; |
| |
| /* |
| * Wakeup any thread waiting for buffer space. |
| */ |
| |
| n = (nd->nd_dpa_in - nd->nd_dpa_out) & DPA_MASK; |
| |
| if (nd->nd_dpa_flag & DPA_WAIT_SPACE && |
| (DPA_MAX - n) > DPA_HIGH_WATER) { |
| nd->nd_dpa_flag &= ~DPA_WAIT_SPACE; |
| wake_up_interruptible(&nd->nd_dpa_wqueue); |
| } |
| |
| done: |
| spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags); |
| return rtn; |
| } |
| |
| static unsigned int dgrp_dpa_select(struct file *file, |
| struct poll_table_struct *table) |
| { |
| unsigned int retval = 0; |
| struct nd_struct *nd = file->private_data; |
| |
| if (nd->nd_dpa_out != nd->nd_dpa_in) |
| retval |= POLLIN | POLLRDNORM; /* Conditionally readable */ |
| |
| retval |= POLLOUT | POLLWRNORM; /* Always writeable */ |
| |
| return retval; |
| } |
| |
| static long dgrp_dpa_ioctl(struct file *file, unsigned int cmd, |
| unsigned long arg) |
| { |
| |
| struct nd_struct *nd; |
| struct digi_chan getchan; |
| struct digi_node getnode; |
| struct ch_struct *ch; |
| struct digi_debug setdebug; |
| struct digi_vpd vpd; |
| unsigned int port; |
| void __user *uarg = (void __user *) arg; |
| |
| nd = file->private_data; |
| |
| switch (cmd) { |
| case DIGI_GETCHAN: |
| if (copy_from_user(&getchan, uarg, sizeof(struct digi_chan))) |
| return -EFAULT; |
| |
| port = getchan.ch_port; |
| |
| if (port > nd->nd_chan_count) |
| return -EINVAL; |
| |
| ch = nd->nd_chan + port; |
| |
| getchan.ch_open = (ch->ch_open_count > 0) ? 1 : 0; |
| getchan.ch_txcount = ch->ch_txcount; |
| getchan.ch_rxcount = ch->ch_rxcount; |
| getchan.ch_s_brate = ch->ch_s_brate; |
| getchan.ch_s_estat = ch->ch_s_elast; |
| getchan.ch_s_cflag = ch->ch_s_cflag; |
| getchan.ch_s_iflag = ch->ch_s_iflag; |
| getchan.ch_s_oflag = ch->ch_s_oflag; |
| getchan.ch_s_xflag = ch->ch_s_xflag; |
| getchan.ch_s_mstat = ch->ch_s_mlast; |
| |
| if (copy_to_user(uarg, &getchan, sizeof(struct digi_chan))) |
| return -EFAULT; |
| break; |
| |
| |
| case DIGI_GETNODE: |
| getnode.nd_state = (nd->nd_state & NS_READY) ? 1 : 0; |
| getnode.nd_chan_count = nd->nd_chan_count; |
| getnode.nd_tx_byte = nd->nd_tx_byte; |
| getnode.nd_rx_byte = nd->nd_rx_byte; |
| |
| memset(&getnode.nd_ps_desc, 0, MAX_DESC_LEN); |
| strlcpy(getnode.nd_ps_desc, nd->nd_ps_desc, MAX_DESC_LEN); |
| |
| if (copy_to_user(uarg, &getnode, sizeof(struct digi_node))) |
| return -EFAULT; |
| break; |
| |
| |
| case DIGI_SETDEBUG: |
| if (copy_from_user(&setdebug, uarg, sizeof(struct digi_debug))) |
| return -EFAULT; |
| |
| nd->nd_dpa_debug = setdebug.onoff; |
| nd->nd_dpa_port = setdebug.port; |
| break; |
| |
| |
| case DIGI_GETVPD: |
| memset(&vpd, 0, sizeof(vpd)); |
| if (nd->nd_vpd_len > 0) { |
| vpd.vpd_len = nd->nd_vpd_len; |
| memcpy(&vpd.vpd_data, &nd->nd_vpd, nd->nd_vpd_len); |
| } else { |
| vpd.vpd_len = 0; |
| } |
| |
| if (copy_to_user(uarg, &vpd, sizeof(struct digi_vpd))) |
| return -EFAULT; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * dgrp_dpa() -- send data to the device monitor queue |
| * @nd: pointer to a node structure |
| * @buf: buffer of data to copy to the monitoring buffer |
| * @len: number of bytes to transfer to the buffer |
| * |
| * Called by the net device routines to send data to the device |
| * monitor queue. If the device monitor buffer is too full to |
| * accept the data, it waits until the buffer is ready. |
| */ |
| static void dgrp_dpa(struct nd_struct *nd, u8 *buf, int nbuf) |
| { |
| int n; |
| int r; |
| unsigned long lock_flags; |
| |
| /* |
| * Grab DPA lock. |
| */ |
| spin_lock_irqsave(&nd->nd_dpa_lock, lock_flags); |
| |
| /* |
| * Loop while data remains. |
| */ |
| while (nbuf > 0 && nd->nd_dpa_buf != NULL) { |
| |
| n = (nd->nd_dpa_out - nd->nd_dpa_in - 1) & DPA_MASK; |
| |
| /* |
| * Enforce flow control on the DPA device. |
| */ |
| if (n < (DPA_MAX - DPA_HIGH_WATER)) |
| nd->nd_dpa_flag |= DPA_WAIT_SPACE; |
| |
| /* |
| * This should never happen, as the flow control above |
| * should have stopped things before they got to this point. |
| */ |
| if (n == 0) { |
| spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags); |
| return; |
| } |
| |
| /* |
| * Copy as much data as will fit. |
| */ |
| |
| if (n > nbuf) |
| n = nbuf; |
| |
| r = DPA_MAX - nd->nd_dpa_in; |
| |
| if (r <= n) { |
| memcpy(nd->nd_dpa_buf + nd->nd_dpa_in, buf, r); |
| |
| n -= r; |
| |
| nd->nd_dpa_in = 0; |
| |
| buf += r; |
| nbuf -= r; |
| } |
| |
| memcpy(nd->nd_dpa_buf + nd->nd_dpa_in, buf, n); |
| |
| nd->nd_dpa_in += n; |
| |
| buf += n; |
| nbuf -= n; |
| |
| if (nd->nd_dpa_in >= DPA_MAX) |
| pr_info_ratelimited("%s - nd->nd_dpa_in (%i) >= DPA_MAX\n", |
| __func__, nd->nd_dpa_in); |
| |
| /* |
| * Wakeup any thread waiting for data |
| */ |
| if (nd->nd_dpa_flag & DPA_WAIT_DATA) { |
| nd->nd_dpa_flag &= ~DPA_WAIT_DATA; |
| wake_up_interruptible(&nd->nd_dpa_wqueue); |
| } |
| } |
| |
| /* |
| * Release the DPA lock. |
| */ |
| spin_unlock_irqrestore(&nd->nd_dpa_lock, lock_flags); |
| } |
| |
| /** |
| * dgrp_monitor_data() -- builds a DPA data packet |
| * @nd: pointer to a node structure |
| * @type: type of message to be logged in the DPA buffer |
| * @buf: buffer of data to be logged in the DPA buffer |
| * @size -- number of bytes in the "buf" buffer |
| */ |
| void dgrp_dpa_data(struct nd_struct *nd, int type, u8 *buf, int size) |
| { |
| u8 header[5]; |
| |
| header[0] = type; |
| |
| put_unaligned_be32(size, header + 1); |
| |
| dgrp_dpa(nd, header, sizeof(header)); |
| dgrp_dpa(nd, buf, size); |
| } |