| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 1 | /* | 
|  | 2 | *  scsi_netlink.c  - SCSI Transport Netlink Interface | 
|  | 3 | * | 
|  | 4 | *  Copyright (C) 2006   James Smart, Emulex Corporation | 
|  | 5 | * | 
|  | 6 | *  This program is free software; you can redistribute it and/or modify | 
|  | 7 | *  it under the terms of the GNU General Public License as published by | 
|  | 8 | *  the Free Software Foundation; either version 2 of the License, or | 
|  | 9 | *  (at your option) any later version. | 
|  | 10 | * | 
|  | 11 | *  This program is distributed in the hope that it will be useful, | 
|  | 12 | *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 13 | *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | 14 | *  GNU General Public License for more details. | 
|  | 15 | * | 
|  | 16 | *  You should have received a copy of the GNU General Public License | 
|  | 17 | *  along with this program; if not, write to the Free Software | 
|  | 18 | *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
|  | 19 | * | 
|  | 20 | */ | 
|  | 21 | #include <linux/time.h> | 
|  | 22 | #include <linux/jiffies.h> | 
|  | 23 | #include <linux/security.h> | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 24 | #include <linux/delay.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 25 | #include <linux/slab.h> | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 26 | #include <net/sock.h> | 
|  | 27 | #include <net/netlink.h> | 
|  | 28 |  | 
|  | 29 | #include <scsi/scsi_netlink.h> | 
|  | 30 | #include "scsi_priv.h" | 
|  | 31 |  | 
|  | 32 | struct sock *scsi_nl_sock = NULL; | 
|  | 33 | EXPORT_SYMBOL_GPL(scsi_nl_sock); | 
|  | 34 |  | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 35 | static DEFINE_SPINLOCK(scsi_nl_lock); | 
|  | 36 | static struct list_head scsi_nl_drivers; | 
|  | 37 |  | 
|  | 38 | static u32	scsi_nl_state; | 
|  | 39 | #define STATE_EHANDLER_BSY		0x00000001 | 
|  | 40 |  | 
|  | 41 | struct scsi_nl_transport { | 
|  | 42 | int (*msg_handler)(struct sk_buff *); | 
|  | 43 | void (*event_handler)(struct notifier_block *, unsigned long, void *); | 
|  | 44 | unsigned int refcnt; | 
|  | 45 | int flags; | 
|  | 46 | }; | 
|  | 47 |  | 
|  | 48 | /* flags values (bit flags) */ | 
|  | 49 | #define HANDLER_DELETING		0x1 | 
|  | 50 |  | 
|  | 51 | static struct scsi_nl_transport transports[SCSI_NL_MAX_TRANSPORTS] = | 
|  | 52 | { {NULL, }, }; | 
|  | 53 |  | 
|  | 54 |  | 
|  | 55 | struct scsi_nl_drvr { | 
|  | 56 | struct list_head next; | 
|  | 57 | int (*dmsg_handler)(struct Scsi_Host *shost, void *payload, | 
|  | 58 | u32 len, u32 pid); | 
|  | 59 | void (*devt_handler)(struct notifier_block *nb, | 
|  | 60 | unsigned long event, void *notify_ptr); | 
|  | 61 | struct scsi_host_template *hostt; | 
|  | 62 | u64 vendor_id; | 
|  | 63 | unsigned int refcnt; | 
|  | 64 | int flags; | 
|  | 65 | }; | 
|  | 66 |  | 
|  | 67 |  | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 68 |  | 
|  | 69 | /** | 
| Rob Landley | eb44820 | 2007-11-03 13:30:39 -0500 | [diff] [blame] | 70 | * scsi_nl_rcv_msg - Receive message handler. | 
|  | 71 | * @skb:		socket receive buffer | 
|  | 72 | * | 
|  | 73 | * Description: Extracts message from a receive buffer. | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 74 | *    Validates message header and calls appropriate transport message handler | 
|  | 75 | * | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 76 | * | 
|  | 77 | **/ | 
|  | 78 | static void | 
|  | 79 | scsi_nl_rcv_msg(struct sk_buff *skb) | 
|  | 80 | { | 
|  | 81 | struct nlmsghdr *nlh; | 
|  | 82 | struct scsi_nl_hdr *hdr; | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 83 | unsigned long flags; | 
|  | 84 | u32 rlen; | 
|  | 85 | int err, tport; | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 86 |  | 
|  | 87 | while (skb->len >= NLMSG_SPACE(0)) { | 
|  | 88 | err = 0; | 
|  | 89 |  | 
| Arnaldo Carvalho de Melo | b529ccf | 2007-04-25 19:08:35 -0700 | [diff] [blame] | 90 | nlh = nlmsg_hdr(skb); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 91 | if ((nlh->nlmsg_len < (sizeof(*nlh) + sizeof(*hdr))) || | 
|  | 92 | (skb->len < nlh->nlmsg_len)) { | 
|  | 93 | printk(KERN_WARNING "%s: discarding partial skb\n", | 
| Harvey Harrison | cadbd4a | 2008-07-03 23:47:27 -0700 | [diff] [blame] | 94 | __func__); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 95 | return; | 
|  | 96 | } | 
|  | 97 |  | 
|  | 98 | rlen = NLMSG_ALIGN(nlh->nlmsg_len); | 
|  | 99 | if (rlen > skb->len) | 
|  | 100 | rlen = skb->len; | 
|  | 101 |  | 
|  | 102 | if (nlh->nlmsg_type != SCSI_TRANSPORT_MSG) { | 
|  | 103 | err = -EBADMSG; | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 104 | goto next_msg; | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 105 | } | 
|  | 106 |  | 
|  | 107 | hdr = NLMSG_DATA(nlh); | 
|  | 108 | if ((hdr->version != SCSI_NL_VERSION) || | 
|  | 109 | (hdr->magic != SCSI_NL_MAGIC)) { | 
|  | 110 | err = -EPROTOTYPE; | 
|  | 111 | goto next_msg; | 
|  | 112 | } | 
|  | 113 |  | 
|  | 114 | if (security_netlink_recv(skb, CAP_SYS_ADMIN)) { | 
|  | 115 | err = -EPERM; | 
|  | 116 | goto next_msg; | 
|  | 117 | } | 
|  | 118 |  | 
|  | 119 | if (nlh->nlmsg_len < (sizeof(*nlh) + hdr->msglen)) { | 
|  | 120 | printk(KERN_WARNING "%s: discarding partial message\n", | 
| Harvey Harrison | cadbd4a | 2008-07-03 23:47:27 -0700 | [diff] [blame] | 121 | __func__); | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 122 | goto next_msg; | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 123 | } | 
|  | 124 |  | 
|  | 125 | /* | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 126 | * Deliver message to the appropriate transport | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 127 | */ | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 128 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 129 |  | 
|  | 130 | tport = hdr->transport; | 
|  | 131 | if ((tport < SCSI_NL_MAX_TRANSPORTS) && | 
|  | 132 | !(transports[tport].flags & HANDLER_DELETING) && | 
|  | 133 | (transports[tport].msg_handler)) { | 
|  | 134 | transports[tport].refcnt++; | 
|  | 135 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 136 | err = transports[tport].msg_handler(skb); | 
|  | 137 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 138 | transports[tport].refcnt--; | 
|  | 139 | } else | 
|  | 140 | err = -ENOENT; | 
|  | 141 |  | 
|  | 142 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 143 |  | 
|  | 144 | next_msg: | 
|  | 145 | if ((err) || (nlh->nlmsg_flags & NLM_F_ACK)) | 
|  | 146 | netlink_ack(skb, nlh, err); | 
|  | 147 |  | 
|  | 148 | skb_pull(skb, rlen); | 
|  | 149 | } | 
|  | 150 | } | 
|  | 151 |  | 
|  | 152 |  | 
|  | 153 | /** | 
| Rob Landley | eb44820 | 2007-11-03 13:30:39 -0500 | [diff] [blame] | 154 | * scsi_nl_rcv_event - Event handler for a netlink socket. | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 155 | * @this:		event notifier block | 
|  | 156 | * @event:		event type | 
|  | 157 | * @ptr:		event payload | 
|  | 158 | * | 
|  | 159 | **/ | 
|  | 160 | static int | 
|  | 161 | scsi_nl_rcv_event(struct notifier_block *this, unsigned long event, void *ptr) | 
|  | 162 | { | 
|  | 163 | struct netlink_notify *n = ptr; | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 164 | struct scsi_nl_drvr *driver; | 
|  | 165 | unsigned long flags; | 
|  | 166 | int tport; | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 167 |  | 
|  | 168 | if (n->protocol != NETLINK_SCSITRANSPORT) | 
|  | 169 | return NOTIFY_DONE; | 
|  | 170 |  | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 171 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 172 | scsi_nl_state |= STATE_EHANDLER_BSY; | 
|  | 173 |  | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 174 | /* | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 175 | * Pass event on to any transports that may be listening | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 176 | */ | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 177 | for (tport = 0; tport < SCSI_NL_MAX_TRANSPORTS; tport++) { | 
|  | 178 | if (!(transports[tport].flags & HANDLER_DELETING) && | 
|  | 179 | (transports[tport].event_handler)) { | 
|  | 180 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 181 | transports[tport].event_handler(this, event, ptr); | 
|  | 182 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 183 | } | 
|  | 184 | } | 
|  | 185 |  | 
|  | 186 | /* | 
|  | 187 | * Pass event on to any drivers that may be listening | 
|  | 188 | */ | 
|  | 189 | list_for_each_entry(driver, &scsi_nl_drivers, next) { | 
|  | 190 | if (!(driver->flags & HANDLER_DELETING) && | 
|  | 191 | (driver->devt_handler)) { | 
|  | 192 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 193 | driver->devt_handler(this, event, ptr); | 
|  | 194 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 195 | } | 
|  | 196 | } | 
|  | 197 |  | 
|  | 198 | scsi_nl_state &= ~STATE_EHANDLER_BSY; | 
|  | 199 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 200 |  | 
|  | 201 | return NOTIFY_DONE; | 
|  | 202 | } | 
|  | 203 |  | 
|  | 204 | static struct notifier_block scsi_netlink_notifier = { | 
|  | 205 | .notifier_call  = scsi_nl_rcv_event, | 
|  | 206 | }; | 
|  | 207 |  | 
|  | 208 |  | 
| Randy Dunlap | aa198bf | 2008-10-13 09:15:09 -0700 | [diff] [blame] | 209 | /* | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 210 | * GENERIC SCSI transport receive and event handlers | 
| Randy Dunlap | aa198bf | 2008-10-13 09:15:09 -0700 | [diff] [blame] | 211 | */ | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 212 |  | 
|  | 213 | /** | 
| Randy Dunlap | aa198bf | 2008-10-13 09:15:09 -0700 | [diff] [blame] | 214 | * scsi_generic_msg_handler - receive message handler for GENERIC transport messages | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 215 | * @skb:		socket receive buffer | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 216 | **/ | 
|  | 217 | static int | 
|  | 218 | scsi_generic_msg_handler(struct sk_buff *skb) | 
|  | 219 | { | 
|  | 220 | struct nlmsghdr *nlh = nlmsg_hdr(skb); | 
|  | 221 | struct scsi_nl_hdr *snlh = NLMSG_DATA(nlh); | 
|  | 222 | struct scsi_nl_drvr *driver; | 
|  | 223 | struct Scsi_Host *shost; | 
|  | 224 | unsigned long flags; | 
|  | 225 | int err = 0, match, pid; | 
|  | 226 |  | 
|  | 227 | pid = NETLINK_CREDS(skb)->pid; | 
|  | 228 |  | 
|  | 229 | switch (snlh->msgtype) { | 
|  | 230 | case SCSI_NL_SHOST_VENDOR: | 
|  | 231 | { | 
|  | 232 | struct scsi_nl_host_vendor_msg *msg = NLMSG_DATA(nlh); | 
|  | 233 |  | 
|  | 234 | /* Locate the driver that corresponds to the message */ | 
|  | 235 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 236 | match = 0; | 
|  | 237 | list_for_each_entry(driver, &scsi_nl_drivers, next) { | 
|  | 238 | if (driver->vendor_id == msg->vendor_id) { | 
|  | 239 | match = 1; | 
|  | 240 | break; | 
|  | 241 | } | 
|  | 242 | } | 
|  | 243 |  | 
|  | 244 | if ((!match) || (!driver->dmsg_handler)) { | 
|  | 245 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 246 | err = -ESRCH; | 
|  | 247 | goto rcv_exit; | 
|  | 248 | } | 
|  | 249 |  | 
|  | 250 | if (driver->flags & HANDLER_DELETING) { | 
|  | 251 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 252 | err = -ESHUTDOWN; | 
|  | 253 | goto rcv_exit; | 
|  | 254 | } | 
|  | 255 |  | 
|  | 256 | driver->refcnt++; | 
|  | 257 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 258 |  | 
|  | 259 |  | 
|  | 260 | /* if successful, scsi_host_lookup takes a shost reference */ | 
|  | 261 | shost = scsi_host_lookup(msg->host_no); | 
|  | 262 | if (!shost) { | 
|  | 263 | err = -ENODEV; | 
|  | 264 | goto driver_exit; | 
|  | 265 | } | 
|  | 266 |  | 
|  | 267 | /* is this host owned by the vendor ? */ | 
|  | 268 | if (shost->hostt != driver->hostt) { | 
|  | 269 | err = -EINVAL; | 
|  | 270 | goto vendormsg_put; | 
|  | 271 | } | 
|  | 272 |  | 
|  | 273 | /* pass message on to the driver */ | 
|  | 274 | err = driver->dmsg_handler(shost, (void *)&msg[1], | 
|  | 275 | msg->vmsg_datalen, pid); | 
|  | 276 |  | 
|  | 277 | vendormsg_put: | 
|  | 278 | /* release reference by scsi_host_lookup */ | 
|  | 279 | scsi_host_put(shost); | 
|  | 280 |  | 
|  | 281 | driver_exit: | 
|  | 282 | /* release our own reference on the registration object */ | 
|  | 283 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 284 | driver->refcnt--; | 
|  | 285 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 286 | break; | 
|  | 287 | } | 
|  | 288 |  | 
|  | 289 | default: | 
|  | 290 | err = -EBADR; | 
|  | 291 | break; | 
|  | 292 | } | 
|  | 293 |  | 
|  | 294 | rcv_exit: | 
|  | 295 | if (err) | 
|  | 296 | printk(KERN_WARNING "%s: Msgtype %d failed - err %d\n", | 
|  | 297 | __func__, snlh->msgtype, err); | 
|  | 298 | return err; | 
|  | 299 | } | 
|  | 300 |  | 
|  | 301 |  | 
|  | 302 | /** | 
|  | 303 | * scsi_nl_add_transport - | 
|  | 304 | *    Registers message and event handlers for a transport. Enables | 
|  | 305 | *    receipt of netlink messages and events to a transport. | 
|  | 306 | * | 
|  | 307 | * @tport:		transport registering handlers | 
|  | 308 | * @msg_handler:	receive message handler callback | 
|  | 309 | * @event_handler:	receive event handler callback | 
|  | 310 | **/ | 
|  | 311 | int | 
|  | 312 | scsi_nl_add_transport(u8 tport, | 
|  | 313 | int (*msg_handler)(struct sk_buff *), | 
|  | 314 | void (*event_handler)(struct notifier_block *, unsigned long, void *)) | 
|  | 315 | { | 
|  | 316 | unsigned long flags; | 
|  | 317 | int err = 0; | 
|  | 318 |  | 
|  | 319 | if (tport >= SCSI_NL_MAX_TRANSPORTS) | 
|  | 320 | return -EINVAL; | 
|  | 321 |  | 
|  | 322 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 323 |  | 
|  | 324 | if (scsi_nl_state & STATE_EHANDLER_BSY) { | 
|  | 325 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 326 | msleep(1); | 
|  | 327 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 328 | } | 
|  | 329 |  | 
|  | 330 | if (transports[tport].msg_handler || transports[tport].event_handler) { | 
|  | 331 | err = -EALREADY; | 
|  | 332 | goto register_out; | 
|  | 333 | } | 
|  | 334 |  | 
|  | 335 | transports[tport].msg_handler = msg_handler; | 
|  | 336 | transports[tport].event_handler = event_handler; | 
|  | 337 | transports[tport].flags = 0; | 
|  | 338 | transports[tport].refcnt = 0; | 
|  | 339 |  | 
|  | 340 | register_out: | 
|  | 341 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 342 |  | 
|  | 343 | return err; | 
|  | 344 | } | 
|  | 345 | EXPORT_SYMBOL_GPL(scsi_nl_add_transport); | 
|  | 346 |  | 
|  | 347 |  | 
|  | 348 | /** | 
|  | 349 | * scsi_nl_remove_transport - | 
|  | 350 | *    Disable transport receiption of messages and events | 
|  | 351 | * | 
|  | 352 | * @tport:		transport deregistering handlers | 
|  | 353 | * | 
|  | 354 | **/ | 
|  | 355 | void | 
|  | 356 | scsi_nl_remove_transport(u8 tport) | 
|  | 357 | { | 
|  | 358 | unsigned long flags; | 
|  | 359 |  | 
|  | 360 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 361 | if (scsi_nl_state & STATE_EHANDLER_BSY) { | 
|  | 362 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 363 | msleep(1); | 
|  | 364 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 365 | } | 
|  | 366 |  | 
|  | 367 | if (tport < SCSI_NL_MAX_TRANSPORTS) { | 
|  | 368 | transports[tport].flags |= HANDLER_DELETING; | 
|  | 369 |  | 
|  | 370 | while (transports[tport].refcnt != 0) { | 
|  | 371 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 372 | schedule_timeout_uninterruptible(HZ/4); | 
|  | 373 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 374 | } | 
|  | 375 | transports[tport].msg_handler = NULL; | 
|  | 376 | transports[tport].event_handler = NULL; | 
|  | 377 | transports[tport].flags = 0; | 
|  | 378 | } | 
|  | 379 |  | 
|  | 380 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 381 |  | 
|  | 382 | return; | 
|  | 383 | } | 
|  | 384 | EXPORT_SYMBOL_GPL(scsi_nl_remove_transport); | 
|  | 385 |  | 
|  | 386 |  | 
|  | 387 | /** | 
|  | 388 | * scsi_nl_add_driver - | 
|  | 389 | *    A driver is registering its interfaces for SCSI netlink messages | 
|  | 390 | * | 
|  | 391 | * @vendor_id:          A unique identification value for the driver. | 
|  | 392 | * @hostt:		address of the driver's host template. Used | 
|  | 393 | *			to verify an shost is bound to the driver | 
|  | 394 | * @nlmsg_handler:	receive message handler callback | 
|  | 395 | * @nlevt_handler:	receive event handler callback | 
|  | 396 | * | 
|  | 397 | * Returns: | 
|  | 398 | *   0 on Success | 
|  | 399 | *   error result otherwise | 
|  | 400 | **/ | 
|  | 401 | int | 
|  | 402 | scsi_nl_add_driver(u64 vendor_id, struct scsi_host_template *hostt, | 
|  | 403 | int (*nlmsg_handler)(struct Scsi_Host *shost, void *payload, | 
|  | 404 | u32 len, u32 pid), | 
|  | 405 | void (*nlevt_handler)(struct notifier_block *nb, | 
|  | 406 | unsigned long event, void *notify_ptr)) | 
|  | 407 | { | 
|  | 408 | struct scsi_nl_drvr *driver; | 
|  | 409 | unsigned long flags; | 
|  | 410 |  | 
|  | 411 | driver = kzalloc(sizeof(*driver), GFP_KERNEL); | 
|  | 412 | if (unlikely(!driver)) { | 
|  | 413 | printk(KERN_ERR "%s: allocation failure\n", __func__); | 
|  | 414 | return -ENOMEM; | 
|  | 415 | } | 
|  | 416 |  | 
|  | 417 | driver->dmsg_handler = nlmsg_handler; | 
|  | 418 | driver->devt_handler = nlevt_handler; | 
|  | 419 | driver->hostt = hostt; | 
|  | 420 | driver->vendor_id = vendor_id; | 
|  | 421 |  | 
|  | 422 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 423 | if (scsi_nl_state & STATE_EHANDLER_BSY) { | 
|  | 424 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 425 | msleep(1); | 
|  | 426 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 427 | } | 
|  | 428 | list_add_tail(&driver->next, &scsi_nl_drivers); | 
|  | 429 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 430 |  | 
|  | 431 | return 0; | 
|  | 432 | } | 
|  | 433 | EXPORT_SYMBOL_GPL(scsi_nl_add_driver); | 
|  | 434 |  | 
|  | 435 |  | 
|  | 436 | /** | 
|  | 437 | * scsi_nl_remove_driver - | 
|  | 438 | *    An driver is unregistering with the SCSI netlink messages | 
|  | 439 | * | 
|  | 440 | * @vendor_id:          The unique identification value for the driver. | 
|  | 441 | **/ | 
|  | 442 | void | 
|  | 443 | scsi_nl_remove_driver(u64 vendor_id) | 
|  | 444 | { | 
|  | 445 | struct scsi_nl_drvr *driver; | 
|  | 446 | unsigned long flags; | 
|  | 447 |  | 
|  | 448 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 449 | if (scsi_nl_state & STATE_EHANDLER_BSY) { | 
|  | 450 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 451 | msleep(1); | 
|  | 452 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 453 | } | 
|  | 454 |  | 
|  | 455 | list_for_each_entry(driver, &scsi_nl_drivers, next) { | 
|  | 456 | if (driver->vendor_id == vendor_id) { | 
|  | 457 | driver->flags |= HANDLER_DELETING; | 
|  | 458 | while (driver->refcnt != 0) { | 
|  | 459 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 460 | schedule_timeout_uninterruptible(HZ/4); | 
|  | 461 | spin_lock_irqsave(&scsi_nl_lock, flags); | 
|  | 462 | } | 
|  | 463 | list_del(&driver->next); | 
|  | 464 | kfree(driver); | 
|  | 465 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 466 | return; | 
|  | 467 | } | 
|  | 468 | } | 
|  | 469 |  | 
|  | 470 | spin_unlock_irqrestore(&scsi_nl_lock, flags); | 
|  | 471 |  | 
|  | 472 | printk(KERN_ERR "%s: removal of driver failed - vendor_id 0x%llx\n", | 
|  | 473 | __func__, (unsigned long long)vendor_id); | 
|  | 474 | return; | 
|  | 475 | } | 
|  | 476 | EXPORT_SYMBOL_GPL(scsi_nl_remove_driver); | 
|  | 477 |  | 
|  | 478 |  | 
|  | 479 | /** | 
|  | 480 | * scsi_netlink_init - Called by SCSI subsystem to intialize | 
|  | 481 | * 	the SCSI transport netlink interface | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 482 | * | 
|  | 483 | **/ | 
|  | 484 | void | 
|  | 485 | scsi_netlink_init(void) | 
|  | 486 | { | 
|  | 487 | int error; | 
|  | 488 |  | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 489 | INIT_LIST_HEAD(&scsi_nl_drivers); | 
|  | 490 |  | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 491 | error = netlink_register_notifier(&scsi_netlink_notifier); | 
|  | 492 | if (error) { | 
|  | 493 | printk(KERN_ERR "%s: register of event handler failed - %d\n", | 
| Harvey Harrison | cadbd4a | 2008-07-03 23:47:27 -0700 | [diff] [blame] | 494 | __func__, error); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 495 | return; | 
|  | 496 | } | 
|  | 497 |  | 
| Eric W. Biederman | b4b5102 | 2007-09-12 13:05:38 +0200 | [diff] [blame] | 498 | scsi_nl_sock = netlink_kernel_create(&init_net, NETLINK_SCSITRANSPORT, | 
| Denis V. Lunev | cd40b7d | 2007-10-10 21:15:29 -0700 | [diff] [blame] | 499 | SCSI_NL_GRP_CNT, scsi_nl_rcv_msg, NULL, | 
| Patrick McHardy | af65bdf | 2007-04-20 14:14:21 -0700 | [diff] [blame] | 500 | THIS_MODULE); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 501 | if (!scsi_nl_sock) { | 
|  | 502 | printk(KERN_ERR "%s: register of recieve handler failed\n", | 
| Harvey Harrison | cadbd4a | 2008-07-03 23:47:27 -0700 | [diff] [blame] | 503 | __func__); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 504 | netlink_unregister_notifier(&scsi_netlink_notifier); | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 505 | return; | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 506 | } | 
|  | 507 |  | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 508 | /* Register the entry points for the generic SCSI transport */ | 
|  | 509 | error = scsi_nl_add_transport(SCSI_NL_TRANSPORT, | 
|  | 510 | scsi_generic_msg_handler, NULL); | 
|  | 511 | if (error) | 
|  | 512 | printk(KERN_ERR "%s: register of GENERIC transport handler" | 
|  | 513 | "  failed - %d\n", __func__, error); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 514 | return; | 
|  | 515 | } | 
|  | 516 |  | 
|  | 517 |  | 
|  | 518 | /** | 
| Rob Landley | eb44820 | 2007-11-03 13:30:39 -0500 | [diff] [blame] | 519 | * scsi_netlink_exit - Called by SCSI subsystem to disable the SCSI transport netlink interface | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 520 | * | 
|  | 521 | **/ | 
|  | 522 | void | 
|  | 523 | scsi_netlink_exit(void) | 
|  | 524 | { | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 525 | scsi_nl_remove_transport(SCSI_NL_TRANSPORT); | 
|  | 526 |  | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 527 | if (scsi_nl_sock) { | 
| Denis V. Lunev | b7c6ba6 | 2008-01-28 14:41:19 -0800 | [diff] [blame] | 528 | netlink_kernel_release(scsi_nl_sock); | 
| James Smart | 84314fd | 2006-08-18 17:30:09 -0400 | [diff] [blame] | 529 | netlink_unregister_notifier(&scsi_netlink_notifier); | 
|  | 530 | } | 
|  | 531 |  | 
|  | 532 | return; | 
|  | 533 | } | 
|  | 534 |  | 
|  | 535 |  | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 536 | /* | 
|  | 537 | * Exported Interfaces | 
|  | 538 | */ | 
|  | 539 |  | 
|  | 540 | /** | 
|  | 541 | * scsi_nl_send_transport_msg - | 
|  | 542 | *    Generic function to send a single message from a SCSI transport to | 
|  | 543 | *    a single process | 
|  | 544 | * | 
|  | 545 | * @pid:		receiving pid | 
|  | 546 | * @hdr:		message payload | 
|  | 547 | * | 
|  | 548 | **/ | 
|  | 549 | void | 
|  | 550 | scsi_nl_send_transport_msg(u32 pid, struct scsi_nl_hdr *hdr) | 
|  | 551 | { | 
|  | 552 | struct sk_buff *skb; | 
|  | 553 | struct nlmsghdr	*nlh; | 
|  | 554 | const char *fn; | 
|  | 555 | char *datab; | 
|  | 556 | u32 len, skblen; | 
|  | 557 | int err; | 
|  | 558 |  | 
|  | 559 | if (!scsi_nl_sock) { | 
|  | 560 | err = -ENOENT; | 
|  | 561 | fn = "netlink socket"; | 
|  | 562 | goto msg_fail; | 
|  | 563 | } | 
|  | 564 |  | 
|  | 565 | len = NLMSG_SPACE(hdr->msglen); | 
|  | 566 | skblen = NLMSG_SPACE(len); | 
|  | 567 |  | 
|  | 568 | skb = alloc_skb(skblen, GFP_KERNEL); | 
|  | 569 | if (!skb) { | 
|  | 570 | err = -ENOBUFS; | 
|  | 571 | fn = "alloc_skb"; | 
|  | 572 | goto msg_fail; | 
|  | 573 | } | 
|  | 574 |  | 
|  | 575 | nlh = nlmsg_put(skb, pid, 0, SCSI_TRANSPORT_MSG, len - sizeof(*nlh), 0); | 
|  | 576 | if (!nlh) { | 
|  | 577 | err = -ENOBUFS; | 
|  | 578 | fn = "nlmsg_put"; | 
|  | 579 | goto msg_fail_skb; | 
|  | 580 | } | 
|  | 581 | datab = NLMSG_DATA(nlh); | 
|  | 582 | memcpy(datab, hdr, hdr->msglen); | 
|  | 583 |  | 
|  | 584 | err = nlmsg_unicast(scsi_nl_sock, skb, pid); | 
|  | 585 | if (err < 0) { | 
|  | 586 | fn = "nlmsg_unicast"; | 
|  | 587 | /* nlmsg_unicast already kfree_skb'd */ | 
|  | 588 | goto msg_fail; | 
|  | 589 | } | 
|  | 590 |  | 
|  | 591 | return; | 
|  | 592 |  | 
|  | 593 | msg_fail_skb: | 
|  | 594 | kfree_skb(skb); | 
|  | 595 | msg_fail: | 
|  | 596 | printk(KERN_WARNING | 
|  | 597 | "%s: Dropped Message : pid %d Transport %d, msgtype x%x, " | 
|  | 598 | "msglen %d: %s : err %d\n", | 
|  | 599 | __func__, pid, hdr->transport, hdr->msgtype, hdr->msglen, | 
|  | 600 | fn, err); | 
|  | 601 | return; | 
|  | 602 | } | 
|  | 603 | EXPORT_SYMBOL_GPL(scsi_nl_send_transport_msg); | 
|  | 604 |  | 
|  | 605 |  | 
|  | 606 | /** | 
|  | 607 | * scsi_nl_send_vendor_msg - called to send a shost vendor unique message | 
|  | 608 | *                      to a specific process id. | 
|  | 609 | * | 
|  | 610 | * @pid:		process id of the receiver | 
|  | 611 | * @host_no:		host # sending the message | 
|  | 612 | * @vendor_id:		unique identifier for the driver's vendor | 
|  | 613 | * @data_len:		amount, in bytes, of vendor unique payload data | 
|  | 614 | * @data_buf:		pointer to vendor unique data buffer | 
|  | 615 | * | 
|  | 616 | * Returns: | 
| André Goddard Rosa | af901ca | 2009-11-14 13:09:05 -0200 | [diff] [blame] | 617 | *   0 on successful return | 
| James Smart | 22447be | 2008-08-08 02:14:18 -0400 | [diff] [blame] | 618 | *   otherwise, failing error code | 
|  | 619 | * | 
|  | 620 | * Notes: | 
|  | 621 | *	This routine assumes no locks are held on entry. | 
|  | 622 | */ | 
|  | 623 | int | 
|  | 624 | scsi_nl_send_vendor_msg(u32 pid, unsigned short host_no, u64 vendor_id, | 
|  | 625 | char *data_buf, u32 data_len) | 
|  | 626 | { | 
|  | 627 | struct sk_buff *skb; | 
|  | 628 | struct nlmsghdr	*nlh; | 
|  | 629 | struct scsi_nl_host_vendor_msg *msg; | 
|  | 630 | u32 len, skblen; | 
|  | 631 | int err; | 
|  | 632 |  | 
|  | 633 | if (!scsi_nl_sock) { | 
|  | 634 | err = -ENOENT; | 
|  | 635 | goto send_vendor_fail; | 
|  | 636 | } | 
|  | 637 |  | 
|  | 638 | len = SCSI_NL_MSGALIGN(sizeof(*msg) + data_len); | 
|  | 639 | skblen = NLMSG_SPACE(len); | 
|  | 640 |  | 
|  | 641 | skb = alloc_skb(skblen, GFP_KERNEL); | 
|  | 642 | if (!skb) { | 
|  | 643 | err = -ENOBUFS; | 
|  | 644 | goto send_vendor_fail; | 
|  | 645 | } | 
|  | 646 |  | 
|  | 647 | nlh = nlmsg_put(skb, 0, 0, SCSI_TRANSPORT_MSG, | 
|  | 648 | skblen - sizeof(*nlh), 0); | 
|  | 649 | if (!nlh) { | 
|  | 650 | err = -ENOBUFS; | 
|  | 651 | goto send_vendor_fail_skb; | 
|  | 652 | } | 
|  | 653 | msg = NLMSG_DATA(nlh); | 
|  | 654 |  | 
|  | 655 | INIT_SCSI_NL_HDR(&msg->snlh, SCSI_NL_TRANSPORT, | 
|  | 656 | SCSI_NL_SHOST_VENDOR, len); | 
|  | 657 | msg->vendor_id = vendor_id; | 
|  | 658 | msg->host_no = host_no; | 
|  | 659 | msg->vmsg_datalen = data_len;	/* bytes */ | 
|  | 660 | memcpy(&msg[1], data_buf, data_len); | 
|  | 661 |  | 
|  | 662 | err = nlmsg_unicast(scsi_nl_sock, skb, pid); | 
|  | 663 | if (err) | 
|  | 664 | /* nlmsg_multicast already kfree_skb'd */ | 
|  | 665 | goto send_vendor_fail; | 
|  | 666 |  | 
|  | 667 | return 0; | 
|  | 668 |  | 
|  | 669 | send_vendor_fail_skb: | 
|  | 670 | kfree_skb(skb); | 
|  | 671 | send_vendor_fail: | 
|  | 672 | printk(KERN_WARNING | 
|  | 673 | "%s: Dropped SCSI Msg : host %d vendor_unique - err %d\n", | 
|  | 674 | __func__, host_no, err); | 
|  | 675 | return err; | 
|  | 676 | } | 
|  | 677 | EXPORT_SYMBOL(scsi_nl_send_vendor_msg); | 
|  | 678 |  | 
|  | 679 |  |