diff options
Diffstat (limited to 'drivers/scsi/scsi_transport_iscsi.c')
-rw-r--r-- | drivers/scsi/scsi_transport_iscsi.c | 845 |
1 files changed, 440 insertions, 405 deletions
diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c index 50ed88f98f46..45e31635a595 100644 --- a/drivers/scsi/scsi_transport_iscsi.c +++ b/drivers/scsi/scsi_transport_iscsi.c @@ -21,12 +21,9 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <linux/module.h> -#include <linux/string.h> -#include <linux/slab.h> #include <linux/mempool.h> #include <linux/mutex.h> #include <net/tcp.h> - #include <scsi/scsi.h> #include <scsi/scsi_host.h> #include <scsi/scsi_device.h> @@ -46,11 +43,6 @@ struct iscsi_internal { */ struct list_head sessions; /* - * lock to serialize access to the sessions list which must - * be taken after the rx_queue_mutex - */ - spinlock_t session_lock; - /* * based on transport capabilities, at register time we set these * bits to tell the transport class it wants attributes displayed * in sysfs or that it can support different iSCSI Data-Path @@ -157,7 +149,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 @@ -172,50 +164,270 @@ 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; -}; +static LIST_HEAD(connlist); +static DEFINE_SPINLOCK(connlock); -#define iscsi_dev_to_if_conn(_dev) \ - container_of(_dev, struct iscsi_if_conn, dev) +/* + * 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; -#define iscsi_cdev_to_if_conn(_cdev) \ - iscsi_dev_to_if_conn(_cdev->dev) + shost = iscsi_session_to_shost(session); + scsi_host_put(shost); + kfree(session); + module_put(transport->owner); +} -static LIST_HEAD(connlist); -static DEFINE_SPINLOCK(connlock); +static int iscsi_is_session_dev(const struct device *dev) +{ + return dev->release == iscsi_session_release; +} -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 */ -}; +/** + * 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; + + 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; + + if (scsi_add_host(shost, NULL)) + goto free_host; + + session = iscsi_create_session(shost, transport); + if (!session) + goto remove_host; -#define iscsi_dev_to_if_session(_dev) \ - container_of(_dev, struct iscsi_if_session, dev) + *(unsigned long*)shost->hostdata = (unsigned long)session; + return shost; + +remove_host: + scsi_remove_host(shost); +free_host: + scsi_host_put(shost); + return NULL; +} -#define iscsi_cdev_to_if_session(_cdev) \ - iscsi_dev_to_if_session(_cdev->dev) +EXPORT_SYMBOL_GPL(iscsi_transport_create_session); -#define iscsi_if_session_to_shost(_session) \ - dev_to_shost(_session->dev.parent) +/** + * 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; -static struct iscsi_if_conn* + scsi_remove_host(shost); + session = hostdata_session(shost->hostdata); + 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_cls_conn* iscsi_if_find_conn(uint64_t key) { 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) @@ -250,7 +462,7 @@ static inline struct list_head *skb_to_lh(struct sk_buff *skb) } static void* -mempool_zone_alloc_skb(gfp_t gfp_mask, void *pool_data) +mempool_zone_alloc_skb(unsigned int gfp_mask, void *pool_data) { struct mempool_zone *zone = pool_data; @@ -282,14 +494,21 @@ 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) { + struct mempool_zone *zp; + + zp = kzalloc(sizeof(*zp), GFP_KERNEL); + if (!zp) + return NULL; + zp->pool = mempool_create(max, mempool_zone_alloc_skb, mempool_zone_free_skb, zp); - if (!zp->pool) - return -ENOMEM; + if (!zp->pool) { + kfree(zp); + return NULL; + } zp->size = size; zp->hiwat = hiwat; @@ -298,9 +517,14 @@ mempool_zone_init(struct mempool_zone *zp, unsigned max, unsigned size, spin_lock_init(&zp->freelock); atomic_set(&zp->allocated, 0); - return 0; + 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) @@ -340,7 +564,7 @@ int iscsi_recv_pdu(iscsi_connh_t connh, struct iscsi_hdr *hdr, struct nlmsghdr *nlh; struct sk_buff *skb; struct iscsi_uevent *ev; - struct iscsi_if_conn *conn; + struct iscsi_cls_conn *conn; char *pdu; int len = NLMSG_SPACE(sizeof(*ev) + sizeof(struct iscsi_hdr) + data_size); @@ -348,13 +572,13 @@ int iscsi_recv_pdu(iscsi_connh_t connh, struct iscsi_hdr *hdr, 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); + dev_printk(KERN_ERR, &conn->dev, "iscsi: can not deliver " + "control PDU: OOM\n"); return -ENOMEM; } @@ -363,14 +587,14 @@ 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; 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); @@ -379,18 +603,18 @@ void iscsi_conn_error(iscsi_connh_t connh, enum iscsi_err error) struct nlmsghdr *nlh; struct sk_buff *skb; struct iscsi_uevent *ev; - struct iscsi_if_conn *conn; + struct iscsi_cls_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; } @@ -398,15 +622,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; - 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); @@ -420,9 +644,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; @@ -433,366 +657,197 @@ 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); + 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 | - * *------------------* - */ +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_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; -#define hostdata_privsize(_t) (sizeof(unsigned long) + _t->hostdata_size + \ - _t->hostdata_size % sizeof(unsigned long) + \ - sizeof(struct iscsi_if_session)) + conn = iscsi_if_find_conn(ev->u.get_stats.conn_handle); + if (!conn) + return -EEXIST; -#define hostdata_session(_hostdata) ((void*)_hostdata + sizeof(unsigned long) + \ - ((struct iscsi_transport *) \ - iscsi_ptr(*(uint64_t *)_hostdata))->hostdata_size) + do { + int actual_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; + mempool_zone_complete(conn->z_pdu); - /* 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); + 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; + } + + 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; } static int iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_uevent *ev) { 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; - - /* store struct iscsi_transport in hostdata */ - *(uint64_t*)shost->hostdata = ev->transport_handle; + if (!transport->create_session) + return -EINVAL; - 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; - } + shost = transport->create_session(&priv->t, + ev->u.c_session.initial_cmdsn); + if (!shost) + return -ENOMEM; - /* host_no becomes assigned SID */ + ev->r.c_session_ret.session_handle = iscsi_handle(iscsi_hostdata(shost->hostdata)); 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; - - error = scsi_add_host(shost, NULL); - if (error) - goto out_destroy_session; - - /* - * 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); - - /* 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); - return 0; - -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; } static int iscsi_if_destroy_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; + + if (!transport->destroy_session) + return -EINVAL; shost = scsi_host_lookup(ev->u.d_session.sid); if (shost == ERR_PTR(-ENXIO)) return -EEXIST; - session = hostdata_session(shost->hostdata); - /* 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; - - scsi_host_put(shost); + if (transport->destroy_session) + transport->destroy_session(shost); + /* ref from host lookup */ + scsi_host_put(shost); + return 0; } static int -iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev) -{ - struct iscsi_if_session *session; +iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev){ struct Scsi_Host *shost; - struct iscsi_if_conn *conn; + struct iscsi_cls_conn *conn; unsigned long flags; - int error; + + if (!transport->create_conn) + return -EINVAL; shost = scsi_host_lookup(ev->u.c_conn.sid); if (shost == ERR_PTR(-ENXIO)) return -EEXIST; - session = hostdata_session(shost->hostdata); - 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(shost, ev->u.c_conn.cid); + if (!conn) + goto release_ref; - 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 = conn->connh = iscsi_handle(conn->dd_data); 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: +free_pdu_pool: + mempool_zone_destroy(conn->z_pdu); +destroy_conn: + if (transport->destroy_conn) + transport->destroy_conn(conn->dd_data); +release_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; + 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); if (!conn) return -EEXIST; - transport->destroy_conn(ev->u.d_conn.conn_handle); + if (!transport->destroy_conn) + 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; -} - -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; - - 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; - } - - 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)); + z_pdu = conn->z_pdu; + z_error = conn->z_error; - 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; + if (transport->destroy_conn) + transport->destroy_conn(conn); - err = iscsi_unicast_skb(&conn->z_pdu, skbstat); - } while (err < 0 && err != -ECONNREFUSED); + mempool_zone_destroy(z_pdu); + mempool_zone_destroy(z_error); - return err; + return 0; } static int @@ -916,8 +971,8 @@ 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); @@ -927,6 +982,9 @@ iscsi_if_rx(struct sock *sk, int len) mutex_unlock(&rx_queue_mutex); } +#define iscsi_cdev_to_conn(_cdev) \ + iscsi_dev_to_conn(_cdev->dev) + /* * iSCSI connection attrs */ @@ -935,12 +993,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->dd_data, param, &value); \ return snprintf(buf, 20, format"\n", value); \ } @@ -955,6 +1011,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 */ @@ -963,20 +1022,11 @@ 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 Scsi_Host *shost = iscsi_session_to_shost(session); \ + 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(shost, param, &value); \ return snprintf(buf, 20, format"\n", value); \ } @@ -1005,23 +1055,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; @@ -1032,23 +1077,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; @@ -1059,7 +1102,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; @@ -1069,15 +1113,14 @@ 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); if (!priv) - return -ENOMEM; + return NULL; memset(priv, 0, sizeof(*priv)); 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; @@ -1143,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); @@ -1165,14 +1208,6 @@ int iscsi_unregister_transport(struct iscsi_transport *tt) 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); - mutex_unlock(&rx_queue_mutex); - 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); @@ -1195,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); } @@ -1235,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); @@ -1260,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); |