static int
iscsi_get_host_stats(struct iscsi_transport *transport, struct nlmsghdr *nlh)
{
struct iscsi_uevent *ev = nlmsg_data(nlh);
struct Scsi_Host *shost = NULL;
struct iscsi_internal *priv;
struct sk_buff *skbhost_stats;
struct nlmsghdr *nlhhost_stats;
struct iscsi_uevent *evhost_stats;
int host_stats_size = 0;
int len, err = 0;
char *buf;
if (!transport->get_host_stats)
return -ENOSYS;
priv = iscsi_if_transport_lookup(transport);
if (!priv)
return -EINVAL;
host_stats_size = sizeof(struct iscsi_offload_host_stats);
len = nlmsg_total_size(sizeof(*ev) + host_stats_size);
shost = scsi_host_lookup(ev->u.get_host_stats.host_no);
if (!shost) {
pr_err("%s: failed. Could not find host no %u\n",
__func__, ev->u.get_host_stats.host_no);
return -ENODEV;
}
do {
int actual_size;
skbhost_stats = alloc_skb(len, GFP_KERNEL);
if (!skbhost_stats) {
pr_err("cannot deliver host stats: OOM\n");
err = -ENOMEM;
goto exit_host_stats;
}
nlhhost_stats = __nlmsg_put(skbhost_stats, 0, 0, 0,
(len - sizeof(*nlhhost_stats)), 0);
evhost_stats = nlmsg_data(nlhhost_stats);
memset(evhost_stats, 0, sizeof(*evhost_stats));
evhost_stats->transport_handle = iscsi_handle(transport);
evhost_stats->type = nlh->nlmsg_type;
evhost_stats->u.get_host_stats.host_no =
ev->u.get_host_stats.host_no;
buf = (char *)evhost_stats + sizeof(*evhost_stats);
memset(buf, 0, host_stats_size);
err = transport->get_host_stats(shost, buf, host_stats_size);
if (err) {
kfree_skb(skbhost_stats);
goto exit_host_stats;
}
actual_size = nlmsg_total_size(sizeof(*ev) + host_stats_size);
skb_trim(skbhost_stats, NLMSG_ALIGN(actual_size));
nlhhost_stats->nlmsg_len = actual_size;
err = iscsi_multicast_skb(skbhost_stats, ISCSI_NL_GRP_ISCSID,
GFP_KERNEL);
} while (err < 0 && err != -ECONNREFUSED);
exit_host_stats:
scsi_host_put(shost);
return err;
}
这段代码实现了函数 iscsi_get_host_stats
,用于从 iSCSI 传输层获取主机的统计信息。下面是这个函数的工作流程解释:
iscsi_get_host_stats:
这个函数用于从 iSCSI 传输层获取主机的统计信息。首先,它检查是否存在 transport->get_host_stats
函数。如果不存在,就返回错误码 -ENOSYS。
接下来,函数从传入的 nlmsghdr
参数中获取用户态传递的请求,即请求获取主机统计信息的事件。然后,它从 transport
中查找 iSCSI 传输层的内部数据结构,以便获得传输层的相关信息。
计算主机统计信息结构的大小,并根据这个大小计算需要的整体消息长度。然后,函数根据传入的 host_no
参数查找与主机相关的 Scsi_Host 结构。
在执行获取主机统计信息的操作之前,函数循环分配一个用于存储主机统计信息的 skbhost_stats
,并构建一个对应的 NLMSG 帧。这个 NLMSG 帧中包含事件信息、传输句柄、消息类型等等。
接下来,函数通过调用 transport->get_host_stats
函数获取主机统计信息。如果获取信息失败,函数会释放分配的资源并返回错误码。否则,它会修剪 skbhost_stats
的长度以适应实际大小,并设置 NLMSG 的长度字段。
最后,函数尝试将 skbhost_stats
发送到 iSCSI Netlink 组,如果发送失败会重试,直到发送成功或者出现特定错误。
函数结束时,会释放 Scsi_Host 的引用,并返回执行结果,成功为 0,出错为相应的错误码。
这个函数的目的是从 iSCSI 传输层获取主机的统计信息,并通过 Netlink 通信发送给用户态,以便用户态可以分析和使用这些统计数据。
iscsi_if_transport_conn
static int iscsi_if_transport_conn(struct iscsi_transport *transport,
struct nlmsghdr *nlh)
{
struct iscsi_uevent *ev = nlmsg_data(nlh);
struct iscsi_cls_session *session;
struct iscsi_cls_conn *conn = NULL;
struct iscsi_endpoint *ep;
uint32_t pdu_len;
int err = 0;
switch (nlh->nlmsg_type) {
case ISCSI_UEVENT_CREATE_CONN:
return iscsi_if_create_conn(transport, ev);
case ISCSI_UEVENT_DESTROY_CONN:
return iscsi_if_destroy_conn(transport, ev);
case ISCSI_UEVENT_STOP_CONN:
conn = iscsi_conn_lookup(ev->u.stop_conn.sid,
ev->u.stop_conn.cid);
if (!conn)
return -EINVAL;
return iscsi_if_stop_conn(conn, ev->u.stop_conn.flag);
}
/*
* The following cmds need to be run under the ep_mutex so in kernel
* conn cleanup (ep_disconnect + unbind and conn) is not done while
* these are running. They also must not run if we have just run a conn
* cleanup because they would set the state in a way that might allow
* IO or send IO themselves.
*/
switch (nlh->nlmsg_type) {
case ISCSI_UEVENT_START_CONN:
conn = iscsi_conn_lookup(ev->u.start_conn.sid,
ev->u.start_conn.cid);
break;
case ISCSI_UEVENT_BIND_CONN:
conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid);
break;
case ISCSI_UEVENT_SEND_PDU:
conn = iscsi_conn_lookup(ev->u.send_pdu.sid, ev->u.send_pdu.cid);
break;
}
if (!conn)
return -EINVAL;
mutex_lock(&conn->ep_mutex);
spin_lock_irq(&conn->lock);
if (test_bit(ISCSI_CLS_CONN_BIT_CLEANUP, &conn->flags)) {
spin_unlock_irq(&conn->lock);
mutex_unlock(&conn->ep_mutex);
ev->r.retcode = -ENOTCONN;
return 0;
}
spin_unlock_irq(&conn->lock);
switch (nlh->nlmsg_type) {
case ISCSI_UEVENT_BIND_CONN:
session = iscsi_session_lookup(ev->u.b_conn.sid);
if (!session) {
err = -EINVAL;
break;
}
ev->r.retcode = transport->bind_conn(session, conn,
ev->u.b_conn.transport_eph,
ev->u.b_conn.is_leading);
if (!ev->r.retcode)
WRITE_ONCE(conn->state, ISCSI_CONN_BOUND);
if (ev->r.retcode || !transport->ep_connect)
break;
ep = iscsi_lookup_endpoint(ev->u.b_conn.transport_eph);
if (ep) {
ep->conn = conn;
conn->ep = ep;
iscsi_put_endpoint(ep);
} else {
err = -ENOTCONN;
iscsi_cls_conn_printk(KERN_ERR, conn,
"Could not set ep conn binding\n");
}
break;
case ISCSI_UEVENT_START_CONN:
ev->r.retcode = transport->start_conn(conn);
if (!ev->r.retcode)
WRITE_ONCE(conn->state, ISCSI_CONN_UP);
break;
case ISCSI_UEVENT_SEND_PDU:
pdu_len = nlh->nlmsg_len - sizeof(*nlh) - sizeof(*ev);
if ((ev->u.send_pdu.hdr_size > pdu_len) ||
(ev->u.send_pdu.data_size > (pdu_len - ev->u.send_pdu.hdr_size))) {
err = -EINVAL;
break;
}
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);
break;
default:
err = -ENOSYS;
}
mutex_unlock(&conn->ep_mutex);
return err;
}
这段代码实现了函数 iscsi_if_transport_conn
,用于处理与 iSCSI 连接相关的 Netlink 事件。下面是这个函数的工作流程解释:
iscsi_if_transport_conn:
这个函数处理与 iSCSI 连接相关的 Netlink 事件。它首先从传入的 nlmsghdr
参数中获取用户态传递的请求,即连接事件的类型。然后根据事件类型执行不同的操作。
对于不同类型的连接事件,函数会执行不同的处理:
-
ISCSI_UEVENT_CREATE_CONN:
调用iscsi_if_create_conn
函数来处理创建连接事件。 -
ISCSI_UEVENT_DESTROY_CONN:
调用iscsi_if_destroy_conn
函数来处理销毁连接事件。 -
ISCSI_UEVENT_STOP_CONN:
通过连接的sid
和cid
查找相应的连接,然后调用iscsi_if_stop_conn
函数来停止连接。 -
ISCSI_UEVENT_START_CONN:
通过连接的sid
和cid
查找相应的连接,然后调用transport->start_conn
函数启动连接。 -
ISCSI_UEVENT_BIND_CONN:
通过连接的sid
和cid
查找相应的连接,然后调用transport->bind_conn
函数来绑定连接到传输层的端点。如果绑定成功,将连接的状态设置为ISCSI_CONN_BOUND
。 -
ISCSI_UEVENT_SEND_PDU:
通过连接的sid
和cid
查找相应的连接,然后调用transport->send_pdu
函数来发送 PDU(协议数据单元)。函数会校验 PDU 的有效性。
对于连接事件,函数会获取相应的连接对象,并在执行相关操作之前,通过获取连接的 ep_mutex
锁来确保在运行期间连接不会被清理或修改。在处理结束后,会释放连接的锁。
函数会根据操作的结果返回不同的错误码或执行结果。需要注意的是,函数中会修改返回结构体中的 ev->r.retcode
字段,以反映执行的结果。
这个函数的主要目的是根据不同的连接事件类型,执行相应的操作来管理 iSCSI 连接的创建、启动、停止、绑定和发送 PDU 等操作。
iscsi_if_recv_msg
static int
iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group)
{
int err = 0;
u32 portid;
struct iscsi_uevent *ev = nlmsg_data(nlh);
struct iscsi_transport *transport = NULL;
struct iscsi_internal *priv;
struct iscsi_cls_session *session;
struct iscsi_endpoint *ep = NULL;
if (!netlink_capable(skb, CAP_SYS_ADMIN))
return -EPERM;
if (nlh->nlmsg_type == ISCSI_UEVENT_PATH_UPDATE)
*group = ISCSI_NL_GRP_UIP;
else
*group = ISCSI_NL_GRP_ISCSID;
priv = iscsi_if_transport_lookup(iscsi_ptr(ev->transport_handle));
if (!priv)
return -EINVAL;
transport = priv->iscsi_transport;
if (!try_module_get(transport->owner))
return -EINVAL;
portid = NETLINK_CB(skb).portid;
switch (nlh->nlmsg_type) {
case ISCSI_UEVENT_CREATE_SESSION:
err = iscsi_if_create_session(priv, ep, ev,
portid,
ev->u.c_session.initial_cmdsn,
ev->u.c_session.cmds_max,
ev->u.c_session.queue_depth);
break;
case ISCSI_UEVENT_CREATE_BOUND_SESSION:
ep = iscsi_lookup_endpoint(ev->u.c_bound_session.ep_handle);
if (!ep) {
err = -EINVAL;
break;
}
err = iscsi_if_create_session(priv, ep, ev,
portid,
ev->u.c_bound_session.initial_cmdsn,
ev->u.c_bound_session.cmds_max,
ev->u.c_bound_session.queue_depth);
iscsi_put_endpoint(ep);
break;
case ISCSI_UEVENT_DESTROY_SESSION:
session = iscsi_session_lookup(ev->u.d_session.sid);
if (!session)
err = -EINVAL;
else if (iscsi_session_has_conns(ev->u.d_session.sid))
err = -EBUSY;
else
transport->destroy_session(session);
break;
case ISCSI_UEVENT_DESTROY_SESSION_ASYNC:
session = iscsi_session_lookup(ev->u.d_session.sid);
if (!session)
err = -EINVAL;
else if (iscsi_session_has_conns(ev->u.d_session.sid))
err = -EBUSY;
else {
unsigned long flags;
/* Prevent this session from being found again */
spin_lock_irqsave(&sesslock, flags);
list_del_init(&session->sess_list);
spin_unlock_irqrestore(&sesslock, flags);
queue_work(system_unbound_wq, &session->destroy_work);
}
break;
case ISCSI_UEVENT_UNBIND_SESSION:
session = iscsi_session_lookup(ev->u.d_session.sid);
if (session)
queue_work(session->workq, &session->unbind_work);
else
err = -EINVAL;
break;
case ISCSI_UEVENT_SET_PARAM:
err = iscsi_if_set_param(transport, ev);
break;
case ISCSI_UEVENT_CREATE_CONN:
case ISCSI_UEVENT_DESTROY_CONN:
case ISCSI_UEVENT_STOP_CONN:
case ISCSI_UEVENT_START_CONN:
case ISCSI_UEVENT_BIND_CONN:
case ISCSI_UEVENT_SEND_PDU:
err = iscsi_if_transport_conn(transport, nlh);
break;
case ISCSI_UEVENT_GET_STATS:
err = iscsi_if_get_stats(transport, nlh);
break;
case ISCSI_UEVENT_TRANSPORT_EP_CONNECT:
case ISCSI_UEVENT_TRANSPORT_EP_POLL:
case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT:
case ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST:
err = iscsi_if_transport_ep(transport, ev, nlh->nlmsg_type);
break;
case ISCSI_UEVENT_TGT_DSCVR:
err = iscsi_tgt_dscvr(transport, ev);
break;
case ISCSI_UEVENT_SET_HOST_PARAM:
err = iscsi_set_host_param(transport, ev);
break;
case ISCSI_UEVENT_PATH_UPDATE:
err = iscsi_set_path(transport, ev);
break;
case ISCSI_UEVENT_SET_IFACE_PARAMS:
err = iscsi_set_iface_params(transport, ev,
nlmsg_attrlen(nlh, sizeof(*ev)));
break;
case ISCSI_UEVENT_PING:
err = iscsi_send_ping(transport, ev);
break;
case ISCSI_UEVENT_GET_CHAP:
err = iscsi_get_chap(transport, nlh);
break;
case ISCSI_UEVENT_DELETE_CHAP:
err = iscsi_delete_chap(transport, ev);
break;
case ISCSI_UEVENT_SET_FLASHNODE_PARAMS:
err = iscsi_set_flashnode_param(transport, ev,
nlmsg_attrlen(nlh,
sizeof(*ev)));
break;
case ISCSI_UEVENT_NEW_FLASHNODE:
err = iscsi_new_flashnode(transport, ev,
nlmsg_attrlen(nlh, sizeof(*ev)));
break;
case ISCSI_UEVENT_DEL_FLASHNODE:
err = iscsi_del_flashnode(transport, ev);
break;
case ISCSI_UEVENT_LOGIN_FLASHNODE:
err = iscsi_login_flashnode(transport, ev);
break;
case ISCSI_UEVENT_LOGOUT_FLASHNODE:
err = iscsi_logout_flashnode(transport, ev);
break;
case ISCSI_UEVENT_LOGOUT_FLASHNODE_SID:
err = iscsi_logout_flashnode_sid(transport, ev);
break;
case ISCSI_UEVENT_SET_CHAP:
err = iscsi_set_chap(transport, ev,
nlmsg_attrlen(nlh, sizeof(*ev)));
break;
case ISCSI_UEVENT_GET_HOST_STATS:
err = iscsi_get_host_stats(transport, nlh);
break;
default:
err = -ENOSYS;
break;
}
module_put(transport->owner);
return err;
}
这段代码实现了函数 iscsi_if_recv_msg
,它用于处理从用户空间发送的 Netlink 消息。下面是这个函数的工作流程解释:
iscsi_if_recv_msg:
这个函数负责处理从用户空间发送的 Netlink 消息。首先,它会检查用户是否有足够的权限执行操作。然后,根据接收到的消息类型,执行不同的操作。以下是对不同消息类型的处理步骤:
-
ISCSI_UEVENT_CREATE_SESSION:
调用iscsi_if_create_session
函数创建一个 iSCSI 会话。 -
ISCSI_UEVENT_CREATE_BOUND_SESSION:
查找绑定的端点ep
,然后调用iscsi_if_create_session
函数创建一个 iSCSI 会话。 -
ISCSI_UEVENT_DESTROY_SESSION:
查找并销毁指定的 iSCSI 会话,前提是该会话没有活动的连接。 -
ISCSI_UEVENT_DESTROY_SESSION_ASYNC:
查找并销毁指定的 iSCSI 会话,并将销毁操作放入工作队列中异步处理。 -
ISCSI_UEVENT_UNBIND_SESSION:
查找指定的 iSCSI 会话,并将解绑操作放入会话的工作队列中。 -
ISCSI_UEVENT_SET_PARAM:
调用iscsi_if_set_param
函数设置指定会话的参数。 -
ISCSI_UEVENT_CREATE_CONN、ISCSI_UEVENT_DESTROY_CONN、ISCSI_UEVENT_STOP_CONN、ISCSI_UEVENT_START_CONN、ISCSI_UEVENT_BIND_CONN、ISCSI_UEVENT_SEND_PDU:
调用iscsi_if_transport_conn
函数处理与 iSCSI 连接相关的操作。 -
ISCSI_UEVENT_GET_STATS:
调用iscsi_if_get_stats
函数获取 iSCSI 统计信息。 -
ISCSI_UEVENT_TRANSPORT_EP_CONNECT、ISCSI_UEVENT_TRANSPORT_EP_POLL、ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT、ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST:
调用iscsi_if_transport_ep
函数处理与传输层端点相关的操作。 -
ISCSI_UEVENT_TGT_DSCVR:
调用iscsi_tgt_dscvr
函数执行目标发现操作。 -
ISCSI_UEVENT_SET_HOST_PARAM:
调用iscsi_set_host_param
函数设置主机参数。 -
ISCSI_UEVENT_PATH_UPDATE:
调用iscsi_set_path
函数更新路径信息。 -
ISCSI_UEVENT_SET_IFACE_PARAMS:
调用iscsi_set_iface_params
函数设置接口参数。 -
ISCSI_UEVENT_PING:
调用iscsi_send_ping
函数执行 Ping 操作。 -
ISCSI_UEVENT_GET_CHAP:
调用iscsi_get_chap
函数获取 CHAP 认证信息。 -
ISCSI_UEVENT_DELETE_CHAP:
调用iscsi_delete_chap
函数删除 CHAP 认证信息。 -
ISCSI_UEVENT_SET_FLASHNODE_PARAMS:
调用iscsi_set_flashnode_param
函数设置闪存节点参数。 -
ISCSI_UEVENT_NEW_FLASHNODE:
调用iscsi_new_flashnode
函数创建新的闪存节点。 -
ISCSI_UEVENT_DEL_FLASHNODE:
调用iscsi_del_flashnode
函数删除指定的闪存节点。 -
ISCSI_UEVENT_LOGIN_FLASHNODE:
调用iscsi_login_flashnode
函数执行闪存节点登录操作。 -
ISCSI_UEVENT_LOGOUT_FLASHNODE:
调用iscsi_logout_flashnode
函数执行闪存节点登出操作。 -
ISCSI_UEVENT_LOGOUT_FLASHNODE_SID:
调用iscsi_logout_flashnode_sid
函数根据会话 ID 执行闪存节点登出操作。 -
ISCSI_UEVENT_SET_CHAP:
调用iscsi_set_chap
函数设置 CHAP 认证信息。 -
ISCSI_UEVENT_GET_HOST_STATS:
调用 `iscsi_get_host
_stats` 函数获取主机统计信息。
如果消息类型未在上述处理步骤中列出,则返回错误码 -ENOSYS
,表示不支持的消息类型。
函数执行后会减少传输层的引用计数。每个处理分支都会返回一个错误码,指示操作的成功或失败。这个函数的主要目的是根据不同的消息类型,调用相应的处理函数来执行 iSCSI 相关的操作。