linux/drivers/scsi/scsi_netlink.c
James Smart 84314fd474 [SCSI] SCSI and FC Transport: add netlink support for posting of transport events
This patch formally adds support for the posting of FC events via netlink.
It is a followup to the original RFC at:
  http://marc.theaimsgroup.com/?l=linux-scsi&m=114530667923464&w=2
and the initial posting at:
  http://marc.theaimsgroup.com/?l=linux-scsi&m=115507374832500&w=2

The patch has been updated to optimize the send path, per the discussions
in the initial posting.

Per discussions at the Storage Summit and at OLS, we are to use netlink for
async events from transports. Also per discussions, to avoid a netlink
protocol per transport, I've create a single NETLINK_SCSITRANSPORT protocol,
which can then be used by all transports.

This patch:
- Creates new files scsi_netlink.c and scsi_netlink.h, which contains the
  single and shared definitions for the SCSI Transport. It is tied into the
  base SCSI subsystem intialization.
  Contains a single interface routine, scsi_send_transport_event(), for a
  transport to send an event (via multicast to a protocol specific group).
- Creates a new scsi_netlink_fc.h file, which contains the FC netlink event
  messages
- Adds 3 new routines to the fc transport:
   fc_get_event_number() -  to get a FC event #
   fc_host_post_event()  -  to send a simple FC event (32 bits of data)
   fc_host_post_vendor_event() - to send a Vendor unique event, with
                                 arbitrary amounts of data.

   Note: the separation of event number allows for a LLD to send a standard
     event, followed by vendor-specific data for the event.

Note: This patch assumes 2 prior fc transport patches have been installed:
   http://marc.theaimsgroup.com/?l=linux-scsi&m=115555807316329&w=2
   http://marc.theaimsgroup.com/?l=linux-scsi&m=115581614930261&w=2

   Sorry - next time I'll do something like making these individual
   patches of the same posting when I know they'll be posted closely
   together.

Signed-off-by: James Smart <James.Smart@emulex.com>

Tidy up configuration not to make SCSI always select NET

Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
2006-09-02 15:33:49 -05:00

199 lines
4.2 KiB
C

/*
* scsi_netlink.c - SCSI Transport Netlink Interface
*
* Copyright (C) 2006 James Smart, Emulex 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/time.h>
#include <linux/jiffies.h>
#include <linux/security.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <scsi/scsi_netlink.h>
#include "scsi_priv.h"
struct sock *scsi_nl_sock = NULL;
EXPORT_SYMBOL_GPL(scsi_nl_sock);
/**
* scsi_nl_rcv_msg -
* Receive message handler. Extracts message from a receive buffer.
* Validates message header and calls appropriate transport message handler
*
* @skb: socket receive buffer
*
**/
static void
scsi_nl_rcv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
struct scsi_nl_hdr *hdr;
uint32_t rlen;
int err;
while (skb->len >= NLMSG_SPACE(0)) {
err = 0;
nlh = (struct nlmsghdr *) skb->data;
if ((nlh->nlmsg_len < (sizeof(*nlh) + sizeof(*hdr))) ||
(skb->len < nlh->nlmsg_len)) {
printk(KERN_WARNING "%s: discarding partial skb\n",
__FUNCTION__);
return;
}
rlen = NLMSG_ALIGN(nlh->nlmsg_len);
if (rlen > skb->len)
rlen = skb->len;
if (nlh->nlmsg_type != SCSI_TRANSPORT_MSG) {
err = -EBADMSG;
goto next_msg;
}
hdr = NLMSG_DATA(nlh);
if ((hdr->version != SCSI_NL_VERSION) ||
(hdr->magic != SCSI_NL_MAGIC)) {
err = -EPROTOTYPE;
goto next_msg;
}
if (security_netlink_recv(skb, CAP_SYS_ADMIN)) {
err = -EPERM;
goto next_msg;
}
if (nlh->nlmsg_len < (sizeof(*nlh) + hdr->msglen)) {
printk(KERN_WARNING "%s: discarding partial message\n",
__FUNCTION__);
return;
}
/*
* We currently don't support anyone sending us a message
*/
next_msg:
if ((err) || (nlh->nlmsg_flags & NLM_F_ACK))
netlink_ack(skb, nlh, err);
skb_pull(skb, rlen);
}
}
/**
* scsi_nl_rcv_msg -
* Receive handler for a socket. Extracts a received message buffer from
* the socket, and starts message processing.
*
* @sk: socket
* @len: unused
*
**/
static void
scsi_nl_rcv(struct sock *sk, int len)
{
struct sk_buff *skb;
while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
scsi_nl_rcv_msg(skb);
kfree_skb(skb);
}
}
/**
* scsi_nl_rcv_event -
* Event handler for a netlink socket.
*
* @this: event notifier block
* @event: event type
* @ptr: event payload
*
**/
static int
scsi_nl_rcv_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct netlink_notify *n = ptr;
if (n->protocol != NETLINK_SCSITRANSPORT)
return NOTIFY_DONE;
/*
* Currently, we are not tracking PID's, etc. There is nothing
* to handle.
*/
return NOTIFY_DONE;
}
static struct notifier_block scsi_netlink_notifier = {
.notifier_call = scsi_nl_rcv_event,
};
/**
* scsi_netlink_init -
* Called by SCSI subsystem to intialize the SCSI transport netlink
* interface
*
**/
void
scsi_netlink_init(void)
{
int error;
error = netlink_register_notifier(&scsi_netlink_notifier);
if (error) {
printk(KERN_ERR "%s: register of event handler failed - %d\n",
__FUNCTION__, error);
return;
}
scsi_nl_sock = netlink_kernel_create(NETLINK_SCSITRANSPORT,
SCSI_NL_GRP_CNT, scsi_nl_rcv, THIS_MODULE);
if (!scsi_nl_sock) {
printk(KERN_ERR "%s: register of recieve handler failed\n",
__FUNCTION__);
netlink_unregister_notifier(&scsi_netlink_notifier);
}
return;
}
/**
* scsi_netlink_exit -
* Called by SCSI subsystem to disable the SCSI transport netlink
* interface
*
**/
void
scsi_netlink_exit(void)
{
if (scsi_nl_sock) {
sock_release(scsi_nl_sock->sk_socket);
netlink_unregister_notifier(&scsi_netlink_notifier);
}
return;
}