X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=drivers%2Fscsi%2Fscsi_transport_iscsi.c;h=2730d507e585e511bf368b2d67428a3a516643bd;hb=f01c18456993bab43067b678f56c87ca954aa43b;hp=e08462d50c97e885305b218761407a74911b8f71;hpb=d8bcd8e41576809f276fa44be5012568296cce41;p=powerpc.git diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c index e08462d50c..2730d507e5 100644 --- a/drivers/scsi/scsi_transport_iscsi.c +++ b/drivers/scsi/scsi_transport_iscsi.c @@ -21,11 +21,9 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include -#include -#include #include +#include #include - #include #include #include @@ -40,15 +38,6 @@ struct iscsi_internal { struct scsi_transport_template t; struct iscsi_transport *iscsi_transport; struct list_head list; - /* - * List of sessions for this transport - */ - struct list_head sessions; - /* - * lock to serialize access to the sessions list which must - * be taken after the rx_queue_sema - */ - spinlock_t session_lock; /* * based on transport capabilities, at register time we set these * bits to tell the transport class it wants attributes displayed @@ -70,7 +59,7 @@ struct iscsi_internal { /* * list of registered transports and lock that must * be held while accessing list. The iscsi_transport_lock must - * be acquired after the rx_queue_sema. + * be acquired after the rx_queue_mutex. */ static LIST_HEAD(iscsi_transports); static DEFINE_SPINLOCK(iscsi_transport_lock); @@ -145,7 +134,7 @@ static DECLARE_TRANSPORT_CLASS(iscsi_connection_class, static struct sock *nls; static int daemon_pid; -static DECLARE_MUTEX(rx_queue_sema); +static DEFINE_MUTEX(rx_queue_mutex); struct mempool_zone { mempool_t *pool; @@ -156,7 +145,7 @@ struct mempool_zone { spinlock_t freelock; }; -static struct mempool_zone z_reply; +static struct mempool_zone *z_reply; /* * Z_MAX_* - actual mempool size allocated at the mempool_zone_init() time @@ -171,61 +160,308 @@ static struct mempool_zone z_reply; #define Z_MAX_ERROR 16 #define Z_HIWAT_ERROR 12 -struct iscsi_if_conn { - struct list_head conn_list; /* item in connlist */ - struct list_head session_list; /* item in session->connections */ - iscsi_connh_t connh; - int active; /* must be accessed with the connlock */ - struct Scsi_Host *host; /* originated shost */ - struct device dev; /* sysfs transport/container device */ - struct iscsi_transport *transport; - struct mempool_zone z_error; - struct mempool_zone z_pdu; - struct list_head freequeue; -}; - -#define iscsi_dev_to_if_conn(_dev) \ - container_of(_dev, struct iscsi_if_conn, dev) - -#define iscsi_cdev_to_if_conn(_cdev) \ - iscsi_dev_to_if_conn(_cdev->dev) - +static LIST_HEAD(sesslist); +static DEFINE_SPINLOCK(sesslock); static LIST_HEAD(connlist); static DEFINE_SPINLOCK(connlock); -struct iscsi_if_session { - struct list_head list; /* item in session_list */ - struct list_head connections; - iscsi_sessionh_t sessionh; - struct iscsi_transport *transport; - struct device dev; /* sysfs transport/container device */ -}; - -#define iscsi_dev_to_if_session(_dev) \ - container_of(_dev, struct iscsi_if_session, dev) - -#define iscsi_cdev_to_if_session(_cdev) \ - iscsi_dev_to_if_session(_cdev->dev) +static struct iscsi_cls_session *iscsi_session_lookup(uint64_t handle) +{ + unsigned long flags; + struct iscsi_cls_session *sess; -#define iscsi_if_session_to_shost(_session) \ - dev_to_shost(_session->dev.parent) + spin_lock_irqsave(&sesslock, flags); + list_for_each_entry(sess, &sesslist, sess_list) { + if (sess == iscsi_ptr(handle)) { + spin_unlock_irqrestore(&sesslock, flags); + return sess; + } + } + spin_unlock_irqrestore(&sesslock, flags); + return NULL; +} -static struct iscsi_if_conn* -iscsi_if_find_conn(uint64_t key) +static struct iscsi_cls_conn *iscsi_conn_lookup(uint64_t handle) { unsigned long flags; - struct iscsi_if_conn *conn; + struct iscsi_cls_conn *conn; spin_lock_irqsave(&connlock, flags); - list_for_each_entry(conn, &connlist, conn_list) - if (conn->connh == key) { + list_for_each_entry(conn, &connlist, conn_list) { + if (conn == iscsi_ptr(handle)) { spin_unlock_irqrestore(&connlock, flags); return conn; } + } spin_unlock_irqrestore(&connlock, flags); return NULL; } +/* + * The following functions can be used by LLDs that allocate + * their own scsi_hosts or by software iscsi LLDs + */ +static void iscsi_session_release(struct device *dev) +{ + struct iscsi_cls_session *session = iscsi_dev_to_session(dev); + struct iscsi_transport *transport = session->transport; + struct Scsi_Host *shost; + + shost = iscsi_session_to_shost(session); + scsi_host_put(shost); + kfree(session); + module_put(transport->owner); +} + +static int iscsi_is_session_dev(const struct device *dev) +{ + return dev->release == iscsi_session_release; +} + +/** + * iscsi_create_session - create iscsi class session + * @shost: scsi host + * @transport: iscsi transport + * + * This can be called from a LLD or iscsi_transport + **/ +struct iscsi_cls_session * +iscsi_create_session(struct Scsi_Host *shost, struct iscsi_transport *transport) +{ + struct iscsi_cls_session *session; + int err; + + if (!try_module_get(transport->owner)) + return NULL; + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + goto module_put; + session->transport = transport; + + /* this is released in the dev's release function */ + scsi_host_get(shost); + snprintf(session->dev.bus_id, BUS_ID_SIZE, "session%u", shost->host_no); + session->dev.parent = &shost->shost_gendev; + session->dev.release = iscsi_session_release; + err = device_register(&session->dev); + if (err) { + dev_printk(KERN_ERR, &session->dev, "iscsi: could not " + "register session's dev\n"); + goto free_session; + } + transport_register_device(&session->dev); + + return session; + +free_session: + kfree(session); +module_put: + module_put(transport->owner); + return NULL; +} + +EXPORT_SYMBOL_GPL(iscsi_create_session); + +/** + * iscsi_destroy_session - destroy iscsi session + * @session: iscsi_session + * + * Can be called by a LLD or iscsi_transport. There must not be + * any running connections. + **/ +int iscsi_destroy_session(struct iscsi_cls_session *session) +{ + transport_unregister_device(&session->dev); + device_unregister(&session->dev); + return 0; +} + +EXPORT_SYMBOL_GPL(iscsi_destroy_session); + +static void iscsi_conn_release(struct device *dev) +{ + struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev); + struct device *parent = conn->dev.parent; + + kfree(conn); + put_device(parent); +} + +static int iscsi_is_conn_dev(const struct device *dev) +{ + return dev->release == iscsi_conn_release; +} + +/** + * iscsi_create_conn - create iscsi class connection + * @session: iscsi cls session + * @cid: connection id + * + * This can be called from a LLD or iscsi_transport. The connection + * is child of the session so cid must be unique for all connections + * on the session. + **/ +struct iscsi_cls_conn * +iscsi_create_conn(struct iscsi_cls_session *session, uint32_t cid) +{ + struct iscsi_transport *transport = session->transport; + struct Scsi_Host *shost = iscsi_session_to_shost(session); + struct iscsi_cls_conn *conn; + int err; + + conn = kzalloc(sizeof(*conn) + transport->conndata_size, GFP_KERNEL); + if (!conn) + return NULL; + + if (transport->conndata_size) + conn->dd_data = &conn[1]; + + INIT_LIST_HEAD(&conn->conn_list); + conn->transport = transport; + + /* this is released in the dev's release function */ + if (!get_device(&session->dev)) + goto free_conn; + snprintf(conn->dev.bus_id, BUS_ID_SIZE, "connection%d:%u", + shost->host_no, cid); + conn->dev.parent = &session->dev; + conn->dev.release = iscsi_conn_release; + err = device_register(&conn->dev); + if (err) { + dev_printk(KERN_ERR, &conn->dev, "iscsi: could not register " + "connection's dev\n"); + goto release_parent_ref; + } + transport_register_device(&conn->dev); + return conn; + +release_parent_ref: + put_device(&session->dev); +free_conn: + kfree(conn); + return NULL; +} + +EXPORT_SYMBOL_GPL(iscsi_create_conn); + +/** + * iscsi_destroy_conn - destroy iscsi class connection + * @session: iscsi cls session + * + * This can be called from a LLD or iscsi_transport. + **/ +int iscsi_destroy_conn(struct iscsi_cls_conn *conn) +{ + transport_unregister_device(&conn->dev); + device_unregister(&conn->dev); + return 0; +} + +EXPORT_SYMBOL_GPL(iscsi_destroy_conn); + +/* + * These functions are used only by software iscsi_transports + * which do not allocate and more their scsi_hosts since this + * is initiated from userspace. + */ + +/* + * iSCSI Session's hostdata organization: + * + * *------------------* <== hostdata_session(host->hostdata) + * | ptr to class sess| + * |------------------| <== iscsi_hostdata(host->hostdata) + * | transport's data | + * *------------------* + */ + +#define hostdata_privsize(_t) (sizeof(unsigned long) + _t->hostdata_size + \ + _t->hostdata_size % sizeof(unsigned long)) + +#define hostdata_session(_hostdata) (iscsi_ptr(*(unsigned long *)_hostdata)) + +/** + * iscsi_transport_create_session - create iscsi cls session and host + * scsit: scsi transport template + * transport: iscsi transport template + * + * This can be used by software iscsi_transports that allocate + * a session per scsi host. + **/ +struct Scsi_Host * +iscsi_transport_create_session(struct scsi_transport_template *scsit, + struct iscsi_transport *transport) +{ + struct iscsi_cls_session *session; + struct Scsi_Host *shost; + unsigned long flags; + + shost = scsi_host_alloc(transport->host_template, + hostdata_privsize(transport)); + if (!shost) { + printk(KERN_ERR "iscsi: can not allocate SCSI host for " + "session\n"); + return NULL; + } + + shost->max_id = 1; + shost->max_channel = 0; + shost->max_lun = transport->max_lun; + shost->max_cmd_len = transport->max_cmd_len; + shost->transportt = scsit; + shost->transportt->create_work_queue = 1; + + if (scsi_add_host(shost, NULL)) + goto free_host; + + session = iscsi_create_session(shost, transport); + if (!session) + goto remove_host; + + *(unsigned long*)shost->hostdata = (unsigned long)session; + spin_lock_irqsave(&sesslock, flags); + list_add(&session->sess_list, &sesslist); + spin_unlock_irqrestore(&sesslock, flags); + return shost; + +remove_host: + scsi_remove_host(shost); +free_host: + scsi_host_put(shost); + return NULL; +} + +EXPORT_SYMBOL_GPL(iscsi_transport_create_session); + +/** + * iscsi_transport_destroy_session - destroy session and scsi host + * shost: scsi host + * + * This can be used by software iscsi_transports that allocate + * a session per scsi host. + **/ +int iscsi_transport_destroy_session(struct Scsi_Host *shost) +{ + struct iscsi_cls_session *session; + unsigned long flags; + + scsi_remove_host(shost); + session = hostdata_session(shost->hostdata); + spin_lock_irqsave(&sesslock, flags); + list_del(&session->sess_list); + spin_unlock_irqrestore(&sesslock, flags); + iscsi_destroy_session(session); + /* ref from host alloc */ + scsi_host_put(shost); + return 0; +} + +EXPORT_SYMBOL_GPL(iscsi_transport_destroy_session); + +/* + * iscsi interface functions + */ static struct iscsi_internal * iscsi_if_transport_lookup(struct iscsi_transport *tt) { @@ -281,25 +517,36 @@ mempool_zone_complete(struct mempool_zone *zone) spin_unlock_irqrestore(&zone->freelock, flags); } -static int -mempool_zone_init(struct mempool_zone *zp, unsigned max, unsigned size, - unsigned hiwat) +static struct mempool_zone * +mempool_zone_init(unsigned max, unsigned size, unsigned hiwat) { - zp->pool = mempool_create(max, mempool_zone_alloc_skb, - mempool_zone_free_skb, zp); - if (!zp->pool) - return -ENOMEM; + struct mempool_zone *zp; + + zp = kzalloc(sizeof(*zp), GFP_KERNEL); + if (!zp) + return NULL; zp->size = size; zp->hiwat = hiwat; - INIT_LIST_HEAD(&zp->freequeue); spin_lock_init(&zp->freelock); atomic_set(&zp->allocated, 0); - return 0; + zp->pool = mempool_create(max, mempool_zone_alloc_skb, + mempool_zone_free_skb, zp); + if (!zp->pool) { + kfree(zp); + return NULL; + } + + return zp; } +static void mempool_zone_destroy(struct mempool_zone *zp) +{ + mempool_destroy(zp->pool); + kfree(zp); +} static struct sk_buff* mempool_zone_get_skb(struct mempool_zone *zone) @@ -333,27 +580,23 @@ iscsi_unicast_skb(struct mempool_zone *zone, struct sk_buff *skb) return 0; } -int iscsi_recv_pdu(iscsi_connh_t connh, struct iscsi_hdr *hdr, +int iscsi_recv_pdu(struct iscsi_cls_conn *conn, struct iscsi_hdr *hdr, char *data, uint32_t data_size) { struct nlmsghdr *nlh; struct sk_buff *skb; struct iscsi_uevent *ev; - struct iscsi_if_conn *conn; char *pdu; int len = NLMSG_SPACE(sizeof(*ev) + sizeof(struct iscsi_hdr) + data_size); - conn = iscsi_if_find_conn(connh); - BUG_ON(!conn); - - mempool_zone_complete(&conn->z_pdu); + mempool_zone_complete(conn->z_pdu); - skb = mempool_zone_get_skb(&conn->z_pdu); + skb = mempool_zone_get_skb(conn->z_pdu); if (!skb) { - iscsi_conn_error(connh, ISCSI_ERR_CONN_FAILED); - printk(KERN_ERR "iscsi%d: can not deliver control PDU: OOM\n", - conn->host->host_no); + iscsi_conn_error(conn, ISCSI_ERR_CONN_FAILED); + dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver " + "control PDU: OOM\n"); return -ENOMEM; } @@ -362,34 +605,30 @@ int iscsi_recv_pdu(iscsi_connh_t connh, struct iscsi_hdr *hdr, memset(ev, 0, sizeof(*ev)); ev->transport_handle = iscsi_handle(conn->transport); ev->type = ISCSI_KEVENT_RECV_PDU; - if (atomic_read(&conn->z_pdu.allocated) >= conn->z_pdu.hiwat) + if (atomic_read(&conn->z_pdu->allocated) >= conn->z_pdu->hiwat) ev->iferror = -ENOMEM; - ev->r.recv_req.conn_handle = connh; + ev->r.recv_req.conn_handle = iscsi_handle(conn); pdu = (char*)ev + sizeof(*ev); memcpy(pdu, hdr, sizeof(struct iscsi_hdr)); memcpy(pdu + sizeof(struct iscsi_hdr), data, data_size); - return iscsi_unicast_skb(&conn->z_pdu, skb); + return iscsi_unicast_skb(conn->z_pdu, skb); } EXPORT_SYMBOL_GPL(iscsi_recv_pdu); -void iscsi_conn_error(iscsi_connh_t connh, enum iscsi_err error) +void iscsi_conn_error(struct iscsi_cls_conn *conn, enum iscsi_err error) { struct nlmsghdr *nlh; struct sk_buff *skb; struct iscsi_uevent *ev; - struct iscsi_if_conn *conn; int len = NLMSG_SPACE(sizeof(*ev)); - conn = iscsi_if_find_conn(connh); - BUG_ON(!conn); - - mempool_zone_complete(&conn->z_error); + mempool_zone_complete(conn->z_error); - skb = mempool_zone_get_skb(&conn->z_error); + skb = mempool_zone_get_skb(conn->z_error); if (!skb) { - printk(KERN_ERR "iscsi%d: gracefully ignored conn error (%d)\n", - conn->host->host_no, error); + dev_printk(KERN_ERR, &conn->dev, "iscsi: gracefully ignored " + "conn error (%d)\n", error); return; } @@ -397,15 +636,15 @@ void iscsi_conn_error(iscsi_connh_t connh, enum iscsi_err error) ev = NLMSG_DATA(nlh); ev->transport_handle = iscsi_handle(conn->transport); ev->type = ISCSI_KEVENT_CONN_ERROR; - if (atomic_read(&conn->z_error.allocated) >= conn->z_error.hiwat) + if (atomic_read(&conn->z_error->allocated) >= conn->z_error->hiwat) ev->iferror = -ENOMEM; ev->r.connerror.error = error; - ev->r.connerror.conn_handle = connh; + ev->r.connerror.conn_handle = iscsi_handle(conn); - iscsi_unicast_skb(&conn->z_error, skb); + iscsi_unicast_skb(conn->z_error, skb); - printk(KERN_INFO "iscsi%d: detected conn error (%d)\n", - conn->host->host_no, error); + dev_printk(KERN_INFO, &conn->dev, "iscsi: detected conn error (%d)\n", + error); } EXPORT_SYMBOL_GPL(iscsi_conn_error); @@ -419,9 +658,9 @@ iscsi_if_send_reply(int pid, int seq, int type, int done, int multi, int flags = multi ? NLM_F_MULTI : 0; int t = done ? NLMSG_DONE : type; - mempool_zone_complete(&z_reply); + mempool_zone_complete(z_reply); - skb = mempool_zone_get_skb(&z_reply); + skb = mempool_zone_get_skb(z_reply); /* * FIXME: * user is supposed to react on iferror == -ENOMEM; @@ -432,366 +671,165 @@ iscsi_if_send_reply(int pid, int seq, int type, int done, int multi, nlh = __nlmsg_put(skb, pid, seq, t, (len - sizeof(*nlh)), 0); nlh->nlmsg_flags = flags; memcpy(NLMSG_DATA(nlh), payload, size); - return iscsi_unicast_skb(&z_reply, skb); -} - -/* - * iSCSI Session's hostdata organization: - * - * *------------------* <== host->hostdata - * | transport | - * |------------------| <== iscsi_hostdata(host->hostdata) - * | transport's data | - * |------------------| <== hostdata_session(host->hostdata) - * | interface's data | - * *------------------* - */ - -#define hostdata_privsize(_t) (sizeof(unsigned long) + _t->hostdata_size + \ - _t->hostdata_size % sizeof(unsigned long) + \ - sizeof(struct iscsi_if_session)) - -#define hostdata_session(_hostdata) ((void*)_hostdata + sizeof(unsigned long) + \ - ((struct iscsi_transport *) \ - iscsi_ptr(*(uint64_t *)_hostdata))->hostdata_size) - -static void iscsi_if_session_dev_release(struct device *dev) -{ - struct iscsi_if_session *session = iscsi_dev_to_if_session(dev); - struct iscsi_transport *transport = session->transport; - struct Scsi_Host *shost = iscsi_if_session_to_shost(session); - struct iscsi_if_conn *conn, *tmp; - unsigned long flags; - - /* now free connections */ - spin_lock_irqsave(&connlock, flags); - list_for_each_entry_safe(conn, tmp, &session->connections, - session_list) { - list_del(&conn->session_list); - mempool_destroy(conn->z_pdu.pool); - mempool_destroy(conn->z_error.pool); - kfree(conn); - } - spin_unlock_irqrestore(&connlock, flags); - scsi_host_put(shost); - module_put(transport->owner); + return iscsi_unicast_skb(z_reply, skb); } static int -iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev) +iscsi_if_get_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh) { - struct iscsi_transport *transport = priv->iscsi_transport; - struct iscsi_if_session *session; - struct Scsi_Host *shost; - unsigned long flags; - int error; - - if (!try_module_get(transport->owner)) - return -EPERM; - - shost = scsi_host_alloc(transport->host_template, - hostdata_privsize(transport)); - if (!shost) { - ev->r.c_session_ret.session_handle = iscsi_handle(NULL); - printk(KERN_ERR "iscsi: can not allocate SCSI host for " - "session\n"); - error = -ENOMEM; - goto out_module_put; - } - shost->max_id = 1; - shost->max_channel = 0; - shost->max_lun = transport->max_lun; - shost->max_cmd_len = transport->max_cmd_len; - shost->transportt = &priv->t; + struct iscsi_uevent *ev = NLMSG_DATA(nlh); + struct iscsi_stats *stats; + struct sk_buff *skbstat; + struct iscsi_cls_conn *conn; + struct nlmsghdr *nlhstat; + struct iscsi_uevent *evstat; + int len = NLMSG_SPACE(sizeof(*ev) + + sizeof(struct iscsi_stats) + + sizeof(struct iscsi_stats_custom) * + ISCSI_STATS_CUSTOM_MAX); + int err = 0; - /* store struct iscsi_transport in hostdata */ - *(uint64_t*)shost->hostdata = ev->transport_handle; + conn = iscsi_conn_lookup(ev->u.get_stats.conn_handle); + if (!conn) + return -EEXIST; - ev->r.c_session_ret.session_handle = transport->create_session( - ev->u.c_session.initial_cmdsn, shost); - if (ev->r.c_session_ret.session_handle == iscsi_handle(NULL)) { - error = 0; - goto out_host_put; - } + do { + int actual_size; - /* host_no becomes assigned SID */ - ev->r.c_session_ret.sid = shost->host_no; - /* initialize session */ - session = hostdata_session(shost->hostdata); - INIT_LIST_HEAD(&session->connections); - INIT_LIST_HEAD(&session->list); - session->sessionh = ev->r.c_session_ret.session_handle; - session->transport = transport; + mempool_zone_complete(conn->z_pdu); - error = scsi_add_host(shost, NULL); - if (error) - goto out_destroy_session; + skbstat = mempool_zone_get_skb(conn->z_pdu); + if (!skbstat) { + dev_printk(KERN_ERR, &conn->dev, "iscsi: can not " + "deliver stats: OOM\n"); + return -ENOMEM; + } - /* - * this is released in the dev's release function) - */ - scsi_host_get(shost); - snprintf(session->dev.bus_id, BUS_ID_SIZE, "session%u", shost->host_no); - session->dev.parent = &shost->shost_gendev; - session->dev.release = iscsi_if_session_dev_release; - error = device_register(&session->dev); - if (error) { - printk(KERN_ERR "iscsi: could not register session%d's dev\n", - shost->host_no); - goto out_remove_host; - } - transport_register_device(&session->dev); + nlhstat = __nlmsg_put(skbstat, daemon_pid, 0, 0, + (len - sizeof(*nlhstat)), 0); + evstat = NLMSG_DATA(nlhstat); + memset(evstat, 0, sizeof(*evstat)); + evstat->transport_handle = iscsi_handle(conn->transport); + evstat->type = nlh->nlmsg_type; + if (atomic_read(&conn->z_pdu->allocated) >= conn->z_pdu->hiwat) + evstat->iferror = -ENOMEM; + evstat->u.get_stats.conn_handle = + ev->u.get_stats.conn_handle; + stats = (struct iscsi_stats *) + ((char*)evstat + sizeof(*evstat)); + memset(stats, 0, sizeof(*stats)); - /* add this session to the list of active sessions */ - spin_lock_irqsave(&priv->session_lock, flags); - list_add(&session->list, &priv->sessions); - spin_unlock_irqrestore(&priv->session_lock, flags); + transport->get_stats(conn, stats); + actual_size = NLMSG_SPACE(sizeof(struct iscsi_uevent) + + sizeof(struct iscsi_stats) + + sizeof(struct iscsi_stats_custom) * + stats->custom_length); + actual_size -= sizeof(*nlhstat); + actual_size = NLMSG_LENGTH(actual_size); + skb_trim(skbstat, NLMSG_ALIGN(actual_size)); + nlhstat->nlmsg_len = actual_size; - return 0; + err = iscsi_unicast_skb(conn->z_pdu, skbstat); + } while (err < 0 && err != -ECONNREFUSED); -out_remove_host: - scsi_remove_host(shost); -out_destroy_session: - transport->destroy_session(ev->r.c_session_ret.session_handle); - ev->r.c_session_ret.session_handle = iscsi_handle(NULL); -out_host_put: - scsi_host_put(shost); -out_module_put: - module_put(transport->owner); - return error; + return err; } static int -iscsi_if_destroy_session(struct iscsi_internal *priv, struct iscsi_uevent *ev) +iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev) { struct iscsi_transport *transport = priv->iscsi_transport; - struct Scsi_Host *shost; - struct iscsi_if_session *session; - unsigned long flags; - struct iscsi_if_conn *conn; - int error = 0; - - shost = scsi_host_lookup(ev->u.d_session.sid); - if (shost == ERR_PTR(-ENXIO)) - return -EEXIST; - session = hostdata_session(shost->hostdata); + struct iscsi_cls_session *session; + uint32_t sid; - /* check if we have active connections */ - spin_lock_irqsave(&connlock, flags); - list_for_each_entry(conn, &session->connections, session_list) { - if (conn->active) { - printk(KERN_ERR "iscsi%d: can not destroy session: " - "has active connection (%p)\n", - shost->host_no, iscsi_ptr(conn->connh)); - spin_unlock_irqrestore(&connlock, flags); - error = EIO; - goto out_release_ref; - } - } - spin_unlock_irqrestore(&connlock, flags); - - scsi_remove_host(shost); - transport->destroy_session(ev->u.d_session.session_handle); - transport_unregister_device(&session->dev); - device_unregister(&session->dev); - - /* remove this session from the list of active sessions */ - spin_lock_irqsave(&priv->session_lock, flags); - list_del(&session->list); - spin_unlock_irqrestore(&priv->session_lock, flags); - - /* ref from host alloc */ - scsi_host_put(shost); -out_release_ref: - /* ref from host lookup */ - scsi_host_put(shost); - return error; -} - -static void iscsi_if_conn_dev_release(struct device *dev) -{ - struct iscsi_if_conn *conn = iscsi_dev_to_if_conn(dev); - struct Scsi_Host *shost = conn->host; + session = transport->create_session(&priv->t, + ev->u.c_session.initial_cmdsn, + &sid); + if (!session) + return -ENOMEM; - scsi_host_put(shost); + ev->r.c_session_ret.session_handle = iscsi_handle(session); + ev->r.c_session_ret.sid = sid; + return 0; } static int iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev) { - struct iscsi_if_session *session; - struct Scsi_Host *shost; - struct iscsi_if_conn *conn; + struct iscsi_cls_conn *conn; + struct iscsi_cls_session *session; unsigned long flags; - int error; - shost = scsi_host_lookup(ev->u.c_conn.sid); - if (shost == ERR_PTR(-ENXIO)) - return -EEXIST; - session = hostdata_session(shost->hostdata); + session = iscsi_session_lookup(ev->u.c_conn.session_handle); + if (!session) + return -EINVAL; - conn = kmalloc(sizeof(struct iscsi_if_conn), GFP_KERNEL); - if (!conn) { - error = -ENOMEM; - goto out_release_ref; - } - memset(conn, 0, sizeof(struct iscsi_if_conn)); - INIT_LIST_HEAD(&conn->session_list); - INIT_LIST_HEAD(&conn->conn_list); - conn->host = shost; - conn->transport = transport; + conn = transport->create_conn(session, ev->u.c_conn.cid); + if (!conn) + return -ENOMEM; - error = mempool_zone_init(&conn->z_pdu, Z_MAX_PDU, + conn->z_pdu = mempool_zone_init(Z_MAX_PDU, NLMSG_SPACE(sizeof(struct iscsi_uevent) + sizeof(struct iscsi_hdr) + DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH), Z_HIWAT_PDU); - if (error) { - printk(KERN_ERR "iscsi%d: can not allocate pdu zone for new " - "conn\n", shost->host_no); - goto out_free_conn; + if (!conn->z_pdu) { + dev_printk(KERN_ERR, &conn->dev, "iscsi: can not allocate " + "pdu zone for new conn\n"); + goto destroy_conn; } - error = mempool_zone_init(&conn->z_error, Z_MAX_ERROR, + + conn->z_error = mempool_zone_init(Z_MAX_ERROR, NLMSG_SPACE(sizeof(struct iscsi_uevent)), Z_HIWAT_ERROR); - if (error) { - printk(KERN_ERR "iscsi%d: can not allocate error zone for " - "new conn\n", shost->host_no); - goto out_free_pdu_pool; - } - - ev->r.handle = transport->create_conn(ev->u.c_conn.session_handle, - ev->u.c_conn.cid); - if (!ev->r.handle) { - error = -ENODEV; - goto out_free_error_pool; + if (!conn->z_error) { + dev_printk(KERN_ERR, &conn->dev, "iscsi: can not allocate " + "error zone for new conn\n"); + goto free_pdu_pool; } - conn->connh = ev->r.handle; - - /* - * this is released in the dev's release function - */ - if (!scsi_host_get(shost)) - goto out_destroy_conn; - snprintf(conn->dev.bus_id, BUS_ID_SIZE, "connection%d:%u", - shost->host_no, ev->u.c_conn.cid); - conn->dev.parent = &session->dev; - conn->dev.release = iscsi_if_conn_dev_release; - error = device_register(&conn->dev); - if (error) { - printk(KERN_ERR "iscsi%d: could not register connections%u " - "dev\n", shost->host_no, ev->u.c_conn.cid); - goto out_release_parent_ref; - } - transport_register_device(&conn->dev); + ev->r.handle = iscsi_handle(conn); spin_lock_irqsave(&connlock, flags); list_add(&conn->conn_list, &connlist); - list_add(&conn->session_list, &session->connections); conn->active = 1; spin_unlock_irqrestore(&connlock, flags); - scsi_host_put(shost); return 0; -out_release_parent_ref: - scsi_host_put(shost); -out_destroy_conn: - transport->destroy_conn(ev->r.handle); -out_free_error_pool: - mempool_destroy(conn->z_error.pool); -out_free_pdu_pool: - mempool_destroy(conn->z_pdu.pool); -out_free_conn: - kfree(conn); -out_release_ref: - scsi_host_put(shost); - return error; +free_pdu_pool: + mempool_zone_destroy(conn->z_pdu); +destroy_conn: + if (transport->destroy_conn) + transport->destroy_conn(conn->dd_data); + return -ENOMEM; } static int iscsi_if_destroy_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev) { unsigned long flags; - struct iscsi_if_conn *conn; + struct iscsi_cls_conn *conn; + struct mempool_zone *z_error, *z_pdu; - conn = iscsi_if_find_conn(ev->u.d_conn.conn_handle); + conn = iscsi_conn_lookup(ev->u.d_conn.conn_handle); if (!conn) - return -EEXIST; - - transport->destroy_conn(ev->u.d_conn.conn_handle); - + return -EINVAL; spin_lock_irqsave(&connlock, flags); conn->active = 0; list_del(&conn->conn_list); spin_unlock_irqrestore(&connlock, flags); - transport_unregister_device(&conn->dev); - device_unregister(&conn->dev); - return 0; -} + z_pdu = conn->z_pdu; + z_error = conn->z_error; -static int -iscsi_if_get_stats(struct iscsi_transport *transport, struct sk_buff *skb, - struct nlmsghdr *nlh) -{ - struct iscsi_uevent *ev = NLMSG_DATA(nlh); - struct iscsi_stats *stats; - struct sk_buff *skbstat; - struct iscsi_if_conn *conn; - struct nlmsghdr *nlhstat; - struct iscsi_uevent *evstat; - int len = NLMSG_SPACE(sizeof(*ev) + - sizeof(struct iscsi_stats) + - sizeof(struct iscsi_stats_custom) * - ISCSI_STATS_CUSTOM_MAX); - int err = 0; - - conn = iscsi_if_find_conn(ev->u.get_stats.conn_handle); - if (!conn) - return -EEXIST; + if (transport->destroy_conn) + transport->destroy_conn(conn); - do { - int actual_size; - - mempool_zone_complete(&conn->z_pdu); - - skbstat = mempool_zone_get_skb(&conn->z_pdu); - if (!skbstat) { - printk(KERN_ERR "iscsi%d: can not deliver stats: OOM\n", - conn->host->host_no); - return -ENOMEM; - } + mempool_zone_destroy(z_pdu); + mempool_zone_destroy(z_error); - nlhstat = __nlmsg_put(skbstat, daemon_pid, 0, 0, - (len - sizeof(*nlhstat)), 0); - evstat = NLMSG_DATA(nlhstat); - memset(evstat, 0, sizeof(*evstat)); - evstat->transport_handle = iscsi_handle(conn->transport); - evstat->type = nlh->nlmsg_type; - if (atomic_read(&conn->z_pdu.allocated) >= conn->z_pdu.hiwat) - evstat->iferror = -ENOMEM; - evstat->u.get_stats.conn_handle = - ev->u.get_stats.conn_handle; - stats = (struct iscsi_stats *) - ((char*)evstat + sizeof(*evstat)); - memset(stats, 0, sizeof(*stats)); - - transport->get_stats(ev->u.get_stats.conn_handle, stats); - actual_size = NLMSG_SPACE(sizeof(struct iscsi_uevent) + - sizeof(struct iscsi_stats) + - sizeof(struct iscsi_stats_custom) * - stats->custom_length); - actual_size -= sizeof(*nlhstat); - actual_size = NLMSG_LENGTH(actual_size); - skb_trim(skb, NLMSG_ALIGN(actual_size)); - nlhstat->nlmsg_len = actual_size; - - err = iscsi_unicast_skb(&conn->z_pdu, skbstat); - } while (err < 0 && err != -ECONNREFUSED); - - return err; + return 0; } static int @@ -801,23 +839,27 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) struct iscsi_uevent *ev = NLMSG_DATA(nlh); struct iscsi_transport *transport = NULL; struct iscsi_internal *priv; - - if (NETLINK_CREDS(skb)->uid) - return -EPERM; + struct iscsi_cls_session *session; + struct iscsi_cls_conn *conn; priv = iscsi_if_transport_lookup(iscsi_ptr(ev->transport_handle)); if (!priv) return -EINVAL; transport = priv->iscsi_transport; - daemon_pid = NETLINK_CREDS(skb)->pid; + if (!try_module_get(transport->owner)) + return -EINVAL; switch (nlh->nlmsg_type) { case ISCSI_UEVENT_CREATE_SESSION: err = iscsi_if_create_session(priv, ev); break; case ISCSI_UEVENT_DESTROY_SESSION: - err = iscsi_if_destroy_session(priv, ev); + session = iscsi_session_lookup(ev->u.d_session.session_handle); + if (session) + transport->destroy_session(session); + else + err = -EINVAL; break; case ISCSI_UEVENT_CREATE_CONN: err = iscsi_if_create_conn(transport, ev); @@ -826,63 +868,77 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) err = iscsi_if_destroy_conn(transport, ev); break; case ISCSI_UEVENT_BIND_CONN: - if (!iscsi_if_find_conn(ev->u.b_conn.conn_handle)) - return -EEXIST; - ev->r.retcode = transport->bind_conn( - ev->u.b_conn.session_handle, - ev->u.b_conn.conn_handle, - ev->u.b_conn.transport_fd, - ev->u.b_conn.is_leading); + session = iscsi_session_lookup(ev->u.b_conn.session_handle); + conn = iscsi_conn_lookup(ev->u.b_conn.conn_handle); + + if (session && conn) + ev->r.retcode = transport->bind_conn(session, conn, + ev->u.b_conn.transport_fd, + ev->u.b_conn.is_leading); + else + err = -EINVAL; break; case ISCSI_UEVENT_SET_PARAM: - if (!iscsi_if_find_conn(ev->u.set_param.conn_handle)) - return -EEXIST; - ev->r.retcode = transport->set_param( - ev->u.set_param.conn_handle, - ev->u.set_param.param, ev->u.set_param.value); + conn = iscsi_conn_lookup(ev->u.set_param.conn_handle); + if (conn) + ev->r.retcode = transport->set_param(conn, + ev->u.set_param.param, ev->u.set_param.value); + else + err = -EINVAL; break; case ISCSI_UEVENT_START_CONN: - if (!iscsi_if_find_conn(ev->u.start_conn.conn_handle)) - return -EEXIST; - ev->r.retcode = transport->start_conn( - ev->u.start_conn.conn_handle); + conn = iscsi_conn_lookup(ev->u.start_conn.conn_handle); + if (conn) + ev->r.retcode = transport->start_conn(conn); + else + err = -EINVAL; + break; case ISCSI_UEVENT_STOP_CONN: - if (!iscsi_if_find_conn(ev->u.stop_conn.conn_handle)) - return -EEXIST; - transport->stop_conn(ev->u.stop_conn.conn_handle, - ev->u.stop_conn.flag); + conn = iscsi_conn_lookup(ev->u.stop_conn.conn_handle); + if (conn) + transport->stop_conn(conn, ev->u.stop_conn.flag); + else + err = -EINVAL; break; case ISCSI_UEVENT_SEND_PDU: - if (!iscsi_if_find_conn(ev->u.send_pdu.conn_handle)) - return -EEXIST; - ev->r.retcode = transport->send_pdu( - ev->u.send_pdu.conn_handle, - (struct iscsi_hdr*)((char*)ev + sizeof(*ev)), - (char*)ev + sizeof(*ev) + ev->u.send_pdu.hdr_size, - ev->u.send_pdu.data_size); + conn = iscsi_conn_lookup(ev->u.send_pdu.conn_handle); + if (conn) + ev->r.retcode = transport->send_pdu(conn, + (struct iscsi_hdr*)((char*)ev + sizeof(*ev)), + (char*)ev + sizeof(*ev) + ev->u.send_pdu.hdr_size, + ev->u.send_pdu.data_size); + else + err = -EINVAL; break; case ISCSI_UEVENT_GET_STATS: - err = iscsi_if_get_stats(transport, skb, nlh); + err = iscsi_if_get_stats(transport, nlh); break; default: err = -EINVAL; break; } + module_put(transport->owner); return err; } /* Get message from skb (based on rtnetlink_rcv_skb). Each message is * processed by iscsi_if_recv_msg. Malformed skbs with wrong length are - * discarded silently. */ + * or invalid creds discarded silently. */ static void iscsi_if_rx(struct sock *sk, int len) { struct sk_buff *skb; - down(&rx_queue_sema); + mutex_lock(&rx_queue_mutex); while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) { + if (NETLINK_CREDS(skb)->uid) { + skb_pull(skb, skb->len); + goto free_skb; + } + daemon_pid = NETLINK_CREDS(skb)->pid; + while (skb->len >= NLMSG_SPACE(0)) { int err; uint32_t rlen; @@ -894,10 +950,12 @@ iscsi_if_rx(struct sock *sk, int len) skb->len < nlh->nlmsg_len) { break; } + ev = NLMSG_DATA(nlh); rlen = NLMSG_ALIGN(nlh->nlmsg_len); if (rlen > skb->len) rlen = skb->len; + err = iscsi_if_recv_msg(skb, nlh); if (err) { ev->type = ISCSI_KEVENT_IF_ERROR; @@ -915,17 +973,21 @@ iscsi_if_rx(struct sock *sk, int len) err = iscsi_if_send_reply( NETLINK_CREDS(skb)->pid, nlh->nlmsg_seq, nlh->nlmsg_type, 0, 0, ev, sizeof(*ev)); - if (atomic_read(&z_reply.allocated) >= - z_reply.hiwat) + if (atomic_read(&z_reply->allocated) >= + z_reply->hiwat) ev->iferror = -ENOMEM; } while (err < 0 && err != -ECONNREFUSED); skb_pull(skb, rlen); } +free_skb: kfree_skb(skb); } - up(&rx_queue_sema); + mutex_unlock(&rx_queue_mutex); } +#define iscsi_cdev_to_conn(_cdev) \ + iscsi_dev_to_conn(_cdev->dev) + /* * iSCSI connection attrs */ @@ -934,12 +996,10 @@ static ssize_t \ show_conn_int_param_##param(struct class_device *cdev, char *buf) \ { \ uint32_t value = 0; \ - struct iscsi_if_conn *conn = iscsi_cdev_to_if_conn(cdev); \ - struct iscsi_internal *priv; \ + struct iscsi_cls_conn *conn = iscsi_cdev_to_conn(cdev); \ + struct iscsi_transport *t = conn->transport; \ \ - priv = to_iscsi_internal(conn->host->transportt); \ - if (priv->param_mask & (1 << param)) \ - priv->iscsi_transport->get_param(conn->connh, param, &value); \ + t->get_conn_param(conn, param, &value); \ return snprintf(buf, 20, format"\n", value); \ } @@ -954,6 +1014,9 @@ iscsi_conn_int_attr(data_digest, ISCSI_PARAM_DATADGST_EN, "%d"); iscsi_conn_int_attr(ifmarker, ISCSI_PARAM_IFMARKER_EN, "%d"); iscsi_conn_int_attr(ofmarker, ISCSI_PARAM_OFMARKER_EN, "%d"); +#define iscsi_cdev_to_session(_cdev) \ + iscsi_dev_to_session(_cdev->dev) + /* * iSCSI session attrs */ @@ -962,20 +1025,10 @@ static ssize_t \ show_session_int_param_##param(struct class_device *cdev, char *buf) \ { \ uint32_t value = 0; \ - struct iscsi_if_session *session = iscsi_cdev_to_if_session(cdev); \ - struct Scsi_Host *shost = iscsi_if_session_to_shost(session); \ - struct iscsi_internal *priv = to_iscsi_internal(shost->transportt); \ - struct iscsi_if_conn *conn = NULL; \ - unsigned long flags; \ - \ - spin_lock_irqsave(&connlock, flags); \ - if (!list_empty(&session->connections)) \ - conn = list_entry(session->connections.next, \ - struct iscsi_if_conn, session_list); \ - spin_unlock_irqrestore(&connlock, flags); \ + struct iscsi_cls_session *session = iscsi_cdev_to_session(cdev); \ + struct iscsi_transport *t = session->transport; \ \ - if (conn && (priv->param_mask & (1 << param))) \ - priv->iscsi_transport->get_param(conn->connh, param, &value);\ + t->get_session_param(session, param, &value); \ return snprintf(buf, 20, format"\n", value); \ } @@ -1004,23 +1057,18 @@ iscsi_session_int_attr(erl, ISCSI_PARAM_ERL, "%d"); count++; \ } -static int iscsi_is_session_dev(const struct device *dev) -{ - return dev->release == iscsi_if_session_dev_release; -} - static int iscsi_session_match(struct attribute_container *cont, struct device *dev) { - struct iscsi_if_session *session; + struct iscsi_cls_session *session; struct Scsi_Host *shost; struct iscsi_internal *priv; if (!iscsi_is_session_dev(dev)) return 0; - session = iscsi_dev_to_if_session(dev); - shost = iscsi_if_session_to_shost(session); + session = iscsi_dev_to_session(dev); + shost = iscsi_session_to_shost(session); if (!shost->transportt) return 0; @@ -1031,23 +1079,21 @@ static int iscsi_session_match(struct attribute_container *cont, return &priv->session_cont.ac == cont; } -static int iscsi_is_conn_dev(const struct device *dev) -{ - return dev->release == iscsi_if_conn_dev_release; -} - static int iscsi_conn_match(struct attribute_container *cont, struct device *dev) { - struct iscsi_if_conn *conn; + struct iscsi_cls_session *session; + struct iscsi_cls_conn *conn; struct Scsi_Host *shost; struct iscsi_internal *priv; if (!iscsi_is_conn_dev(dev)) return 0; - conn = iscsi_dev_to_if_conn(dev); - shost = conn->host; + conn = iscsi_dev_to_conn(dev); + session = iscsi_dev_to_session(conn->dev.parent); + shost = iscsi_session_to_shost(session); + if (!shost->transportt) return 0; @@ -1058,7 +1104,8 @@ static int iscsi_conn_match(struct attribute_container *cont, return &priv->conn_cont.ac == cont; } -int iscsi_register_transport(struct iscsi_transport *tt) +struct scsi_transport_template * +iscsi_register_transport(struct iscsi_transport *tt) { struct iscsi_internal *priv; unsigned long flags; @@ -1068,15 +1115,12 @@ int iscsi_register_transport(struct iscsi_transport *tt) priv = iscsi_if_transport_lookup(tt); if (priv) - return -EEXIST; + return NULL; - priv = kmalloc(sizeof(*priv), GFP_KERNEL); + priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) - return -ENOMEM; - memset(priv, 0, sizeof(*priv)); + return NULL; INIT_LIST_HEAD(&priv->list); - INIT_LIST_HEAD(&priv->sessions); - spin_lock_init(&priv->session_lock); priv->iscsi_transport = tt; priv->cdev.class = &iscsi_transport_class; @@ -1142,13 +1186,13 @@ int iscsi_register_transport(struct iscsi_transport *tt) spin_unlock_irqrestore(&iscsi_transport_lock, flags); printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name); - return 0; + return &priv->t; unregister_cdev: class_device_unregister(&priv->cdev); free_priv: kfree(priv); - return err; + return NULL; } EXPORT_SYMBOL_GPL(iscsi_register_transport); @@ -1159,19 +1203,11 @@ int iscsi_unregister_transport(struct iscsi_transport *tt) BUG_ON(!tt); - down(&rx_queue_sema); + mutex_lock(&rx_queue_mutex); priv = iscsi_if_transport_lookup(tt); BUG_ON (!priv); - spin_lock_irqsave(&priv->session_lock, flags); - if (!list_empty(&priv->sessions)) { - spin_unlock_irqrestore(&priv->session_lock, flags); - up(&rx_queue_sema); - return -EPERM; - } - spin_unlock_irqrestore(&priv->session_lock, flags); - spin_lock_irqsave(&iscsi_transport_lock, flags); list_del(&priv->list); spin_unlock_irqrestore(&iscsi_transport_lock, flags); @@ -1181,7 +1217,7 @@ int iscsi_unregister_transport(struct iscsi_transport *tt) sysfs_remove_group(&priv->cdev.kobj, &iscsi_transport_group); class_device_unregister(&priv->cdev); - up(&rx_queue_sema); + mutex_unlock(&rx_queue_mutex); return 0; } @@ -1194,14 +1230,14 @@ iscsi_rcv_nl_event(struct notifier_block *this, unsigned long event, void *ptr) if (event == NETLINK_URELEASE && n->protocol == NETLINK_ISCSI && n->pid) { - struct iscsi_if_conn *conn; + struct iscsi_cls_conn *conn; unsigned long flags; - mempool_zone_complete(&z_reply); + mempool_zone_complete(z_reply); spin_lock_irqsave(&connlock, flags); list_for_each_entry(conn, &connlist, conn_list) { - mempool_zone_complete(&conn->z_error); - mempool_zone_complete(&conn->z_pdu); + mempool_zone_complete(conn->z_error); + mempool_zone_complete(conn->z_pdu); } spin_unlock_irqrestore(&connlock, flags); } @@ -1234,15 +1270,15 @@ static __init int iscsi_transport_init(void) goto unregister_session_class; nls = netlink_kernel_create(NETLINK_ISCSI, 1, iscsi_if_rx, - THIS_MODULE); + THIS_MODULE); if (!nls) { err = -ENOBUFS; goto unregister_notifier; } - err = mempool_zone_init(&z_reply, Z_MAX_REPLY, + z_reply = mempool_zone_init(Z_MAX_REPLY, NLMSG_SPACE(sizeof(struct iscsi_uevent)), Z_HIWAT_REPLY); - if (!err) + if (z_reply) return 0; sock_release(nls->sk_socket); @@ -1259,7 +1295,7 @@ unregister_transport_class: static void __exit iscsi_transport_exit(void) { - mempool_destroy(z_reply.pool); + mempool_zone_destroy(z_reply); sock_release(nls->sk_socket); netlink_unregister_notifier(&iscsi_nl_notifier); transport_class_unregister(&iscsi_connection_class);