blob: f697019774433024818bbade438f4093b31cae06 [file] [log] [blame]
/* bnx2i_hwi.c: Broadcom NetXtreme II iSCSI driver.
*
* Copyright (c) 2006 - 2009 Broadcom Corporation
*
* 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.
*
* Written by: Anil Veerabhadrappa (anilgv@broadcom.com)
*/
#include "bnx2i.h"
#include "bnx2i_ioctl.h"
#include <asm/compat.h>
#ifdef _BNX2I_IOCTL_
static int bnx2i_major_no;
struct tcp_port_mngt bnx2i_tcp_port_tbl;
static DEFINE_SPINLOCK(bnx2i_resc_lock); /* protects global resources */
struct file_operations bnx2i_mgmt_fops;
/**
* bnx2i_alloc_tcp_port - allocates a tcp port from the free list
*
* assumes this function is called with 'bnx2i_resc_lock' held
**/
u16 bnx2i_alloc_tcp_port(void)
{
u16 tcp_port;
if (!bnx2i_tcp_port_tbl.num_free_ports || !bnx2i_tcp_port_tbl.free_q)
return 0;
tcp_port = bnx2i_tcp_port_tbl.free_q[bnx2i_tcp_port_tbl.cons_idx];
bnx2i_tcp_port_tbl.cons_idx++;
bnx2i_tcp_port_tbl.cons_idx %= bnx2i_tcp_port_tbl.max_idx;
bnx2i_tcp_port_tbl.num_free_ports--;
return tcp_port;
}
/**
* bnx2i_free_tcp_port - Frees the given tcp port back to free pool
*
* @port: tcp port number being freed
*
* assumes this function is called with 'bnx2i_resc_lock' held
**/
void bnx2i_free_tcp_port(u16 port)
{
if (!bnx2i_tcp_port_tbl.free_q)
return;
bnx2i_tcp_port_tbl.free_q[bnx2i_tcp_port_tbl.prod_idx] = port;
bnx2i_tcp_port_tbl.prod_idx++;
bnx2i_tcp_port_tbl.prod_idx %= bnx2i_tcp_port_tbl.max_idx;
bnx2i_tcp_port_tbl.num_free_ports++;
}
/**
* bnx2i_tcp_port_new_entry - place 'bnx2id' allocated tcp port number
* to free list
*
* @port: tcp port number being added to free pool
*
* 'bnx2i_resc_lock' is held while operating on global tcp port table
**/
void bnx2i_tcp_port_new_entry(u16 tcp_port)
{
u32 idx = bnx2i_tcp_port_tbl.prod_idx;
spin_lock(&bnx2i_resc_lock);
bnx2i_tcp_port_tbl.free_q[idx] = tcp_port;
bnx2i_tcp_port_tbl.prod_idx++;
bnx2i_tcp_port_tbl.prod_idx %= bnx2i_tcp_port_tbl.max_idx;
bnx2i_tcp_port_tbl.num_free_ports++;
bnx2i_tcp_port_tbl.num_required--;
spin_unlock(&bnx2i_resc_lock);
}
/**
* bnx2i_init_tcp_port_mngr - initializes tcp port manager
*
*/
void bnx2i_init_tcp_port_mngr(void)
{
int mem_size;
bnx2i_tcp_port_tbl.num_free_ports = 0;
bnx2i_tcp_port_tbl.prod_idx = 0;
bnx2i_tcp_port_tbl.cons_idx = 0;
bnx2i_tcp_port_tbl.max_idx = 0;
bnx2i_tcp_port_tbl.num_required = 256;
//printk("bnx2i: ioctl mngr - %d\n", bnx2i_tcp_port_tbl.num_required);
#define BNX2I_MAX_TCP_PORTS 1024
bnx2i_tcp_port_tbl.port_tbl_size = BNX2I_MAX_TCP_PORTS;
mem_size = sizeof(u16) * bnx2i_tcp_port_tbl.port_tbl_size;
if (bnx2i_tcp_port_tbl.port_tbl_size) {
bnx2i_tcp_port_tbl.free_q = kmalloc(mem_size, GFP_KERNEL);
if (bnx2i_tcp_port_tbl.free_q)
bnx2i_tcp_port_tbl.max_idx =
bnx2i_tcp_port_tbl.port_tbl_size;
}
}
/**
* bnx2i_cleanup_tcp_port_mngr - frees memory held by global tcp port table
*
*/
void bnx2i_cleanup_tcp_port_mngr(void)
{
kfree(bnx2i_tcp_port_tbl.free_q);
bnx2i_tcp_port_tbl.free_q = NULL;
bnx2i_tcp_port_tbl.num_free_ports = 0;
}
int bnx2i_check_ioctl_signature(struct bnx2i_ioctl_header *ioc_hdr)
{
if (strcmp(ioc_hdr->signature, BNX2I_MGMT_SIGNATURE))
return -EPERM;
return 0;
}
static int bnx2i_tcp_port_count_ioctl(struct file *file, unsigned long arg)
{
struct bnx2i_get_port_count __user *user_ioc =
(struct bnx2i_get_port_count __user *)arg;
struct bnx2i_get_port_count ioc_req;
int error = 0;
unsigned int count = 0;
//printk("bnx2i: ioctl port req - %d\n", bnx2i_tcp_port_tbl.num_required);
if (copy_from_user(&ioc_req, user_ioc, sizeof(ioc_req))) {
error = -EFAULT;
//printk("bnx2i: ioctl - unable to copy from user\n");
goto out;
}
error = bnx2i_check_ioctl_signature(&ioc_req.hdr);
if (error) {
printk("bnx2i: ioctl - signature check failed!!\n");
goto out;
}
if (bnx2i_tcp_port_tbl.num_free_ports < 10 &&
bnx2i_tcp_port_tbl.num_required) {
if (bnx2i_tcp_port_tbl.num_required < 32) {
count = bnx2i_tcp_port_tbl.num_required;
} else {
count = 32;
}
}
//printk("bnx2i: ioctl - num port %d\n", count);
ioc_req.port_count = count;
if (copy_to_user(&user_ioc->port_count, &ioc_req.port_count,
sizeof(ioc_req.port_count))) {
error = -EFAULT;
goto out;
}
out:
return error;
}
static int bnx2i_tcp_port_ioctl(struct file *file, unsigned long arg)
{
struct bnx2i_set_port_num __user *user_ioc =
(struct bnx2i_set_port_num __user *)arg;
struct bnx2i_set_port_num ioc_req;
struct bnx2i_set_port_num *ioc_req_mp = NULL;
int ioc_msg_size = sizeof(ioc_req);
int error;
int i;
if (copy_from_user(&ioc_req, user_ioc, ioc_msg_size)) {
error = -EFAULT;
goto out;
}
error = bnx2i_check_ioctl_signature(&ioc_req.hdr);
if (error)
goto out;
if (ioc_req.num_ports > 1) {
ioc_msg_size += (ioc_req.num_ports - 1) *
sizeof(ioc_req.tcp_port[0]);
ioc_req_mp = kmalloc(ioc_msg_size, GFP_KERNEL);
if (!ioc_req_mp)
goto out;
if (copy_from_user(ioc_req_mp, user_ioc, ioc_msg_size)) {
error = -EFAULT;
goto out_kfree;
}
}
if (ioc_req.num_ports)
bnx2i_tcp_port_new_entry(ioc_req.tcp_port[0]);
//printk("bnx2i: ioctl - num ports %d\n", ioc_req_mp->num_ports);
i = 1;
while (i < ioc_req_mp->num_ports)
bnx2i_tcp_port_new_entry(ioc_req_mp->tcp_port[i++]);
return 0;
out_kfree:
kfree(ioc_req_mp);
out:
return error;
}
/*
* bnx2i_ioctl_init: initialization routine, registers char driver
*/
int bnx2i_ioctl_init(void)
{
int ret;
/* Register char device node */
ret = register_chrdev(0, "bnx2i", &bnx2i_mgmt_fops);
if (ret < 0) {
printk(KERN_ERR "bnx2i: failed to register device node\n");
return ret;
}
bnx2i_major_no = ret;
bnx2i_init_tcp_port_mngr();
return 0;
}
void bnx2i_ioctl_cleanup(void)
{
if (bnx2i_major_no)
unregister_chrdev(bnx2i_major_no, "bnx2i");
bnx2i_cleanup_tcp_port_mngr();
}
/*
* bnx2i_mgmt_open - "open" entry point
*/
static int bnx2i_mgmt_open(struct inode *inode, struct file *filep)
{
/* only allow access to admin user */
if (!capable(CAP_SYS_ADMIN)) {
return -EACCES;
}
return 0;
}
/*
* bnx2i_mgmt_release- "release" entry point
*/
static int bnx2i_mgmt_release(struct inode *inode, struct file *filep)
{
return 0;
}
/*
* bnx2i_mgmt_ioctl - char driver ioctl entry point
*/
static int bnx2i_mgmt_ioctl(struct inode *node, struct file *file,
unsigned int cmd, unsigned long arg)
{
long rc = 0;
switch (cmd) {
case BNX2I_IOCTL_GET_PORT_REQ:
rc = bnx2i_tcp_port_count_ioctl(file, arg);
break;
case BNX2I_IOCTL_SET_TCP_PORT:
rc = bnx2i_tcp_port_ioctl(file, arg);
break;
default:
printk(KERN_ERR "bnx2i: unknown ioctl cmd %x\n", cmd);
return -ENOTTY;
}
return rc;
}
#ifdef CONFIG_COMPAT
static int bnx2i_tcp_port_count_compat_ioctl(struct file *file, unsigned long arg)
{
struct bnx2i_get_port_count __user *user_ioc =
(struct bnx2i_get_port_count __user *)arg;
struct bnx2i_get_port_count *ioc_req =
compat_alloc_user_space(sizeof(struct bnx2i_get_port_count));
int error;
unsigned int count = 0;
if (clear_user(ioc_req, sizeof(*ioc_req)))
return -EFAULT;
if (copy_in_user(ioc_req, user_ioc, sizeof(*ioc_req))) {
error = -EFAULT;
goto out;
}
error = bnx2i_check_ioctl_signature(&ioc_req->hdr);
if (error)
goto out;
if (bnx2i_tcp_port_tbl.num_free_ports < 10 &&
bnx2i_tcp_port_tbl.num_required) {
if (bnx2i_tcp_port_tbl.num_required < 32)
count = bnx2i_tcp_port_tbl.num_required;
else
count = 32;
}
if (copy_to_user(&ioc_req->port_count, &count,
sizeof(ioc_req->port_count))) {
error = -EFAULT;
goto out;
}
if (copy_in_user(&user_ioc->port_count, &ioc_req->port_count,
sizeof(u32))) {
error = -EFAULT;
goto out;
}
return 0;
out:
return error;
}
static int bnx2i_tcp_port_compat_ioctl(struct file *file, unsigned long arg)
{
struct bnx2i_set_port_num __user *user_ioc =
(struct bnx2i_set_port_num __user *)arg;
struct bnx2i_set_port_num *ioc_req =
compat_alloc_user_space(sizeof(struct bnx2i_set_port_num));
struct bnx2i_set_port_num *ioc_req_mp = NULL;
int ioc_msg_size = sizeof(*ioc_req);
int error;
int i;
if (clear_user(ioc_req, sizeof(*ioc_req)))
return -EFAULT;
if (copy_in_user(ioc_req, user_ioc, ioc_msg_size)) {
error = -EFAULT;
goto out;
}
error = bnx2i_check_ioctl_signature(&ioc_req->hdr);
if (error)
goto out;
if (ioc_req->num_ports > 1) {
ioc_msg_size += (ioc_req->num_ports - 1) *
sizeof(ioc_req->tcp_port[0]);
ioc_req_mp = compat_alloc_user_space(ioc_msg_size);
if (!ioc_req_mp)
goto out;
if (copy_in_user(ioc_req_mp, user_ioc, ioc_msg_size)) {
error = -EFAULT;
goto out;
}
//printk("bnx2i: ioctl - num ports %d\n", ioc_req_mp->num_ports);
i = 0;
while ((i < ioc_req_mp->num_ports) && ioc_req_mp)
bnx2i_tcp_port_new_entry(ioc_req_mp->tcp_port[i++]);
} else if (ioc_req->num_ports == 1)
bnx2i_tcp_port_new_entry(ioc_req->tcp_port[0]);
out:
return error;
}
/*
* bnx2i_mgmt_compat_ioctl - char node ioctl entry point
*/
static long bnx2i_mgmt_compat_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
int rc = -ENOTTY;
switch (cmd) {
case BNX2I_IOCTL_GET_PORT_REQ:
rc = bnx2i_tcp_port_count_compat_ioctl(file, arg);
break;
case BNX2I_IOCTL_SET_TCP_PORT:
rc = bnx2i_tcp_port_compat_ioctl(file, arg);
break;
}
return rc;
}
#endif
/*
* File operations structure - management interface
*/
struct file_operations bnx2i_mgmt_fops = {
.owner = THIS_MODULE,
.open = bnx2i_mgmt_open,
.release = bnx2i_mgmt_release,
.ioctl = bnx2i_mgmt_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = bnx2i_mgmt_compat_ioctl,
#endif
};
#endif /* _BNX2I_IOCTL_ */