SCSI系列三:linux SCSI 子系统四十五-scsi_transport_iscsi(13)


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 参数中获取用户态传递的请求,即连接事件的类型。然后根据事件类型执行不同的操作。

对于不同类型的连接事件,函数会执行不同的处理:

  1. ISCSI_UEVENT_CREATE_CONN:
    调用 iscsi_if_create_conn 函数来处理创建连接事件。

  2. ISCSI_UEVENT_DESTROY_CONN:
    调用 iscsi_if_destroy_conn 函数来处理销毁连接事件。

  3. ISCSI_UEVENT_STOP_CONN:
    通过连接的 sidcid 查找相应的连接,然后调用 iscsi_if_stop_conn 函数来停止连接。

  4. ISCSI_UEVENT_START_CONN:
    通过连接的 sidcid 查找相应的连接,然后调用 transport->start_conn 函数启动连接。

  5. ISCSI_UEVENT_BIND_CONN:
    通过连接的 sidcid 查找相应的连接,然后调用 transport->bind_conn 函数来绑定连接到传输层的端点。如果绑定成功,将连接的状态设置为 ISCSI_CONN_BOUND

  6. ISCSI_UEVENT_SEND_PDU:
    通过连接的 sidcid 查找相应的连接,然后调用 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 消息。首先,它会检查用户是否有足够的权限执行操作。然后,根据接收到的消息类型,执行不同的操作。以下是对不同消息类型的处理步骤:

  1. ISCSI_UEVENT_CREATE_SESSION:
    调用 iscsi_if_create_session 函数创建一个 iSCSI 会话。

  2. ISCSI_UEVENT_CREATE_BOUND_SESSION:
    查找绑定的端点 ep,然后调用 iscsi_if_create_session 函数创建一个 iSCSI 会话。

  3. ISCSI_UEVENT_DESTROY_SESSION:
    查找并销毁指定的 iSCSI 会话,前提是该会话没有活动的连接。

  4. ISCSI_UEVENT_DESTROY_SESSION_ASYNC:
    查找并销毁指定的 iSCSI 会话,并将销毁操作放入工作队列中异步处理。

  5. ISCSI_UEVENT_UNBIND_SESSION:
    查找指定的 iSCSI 会话,并将解绑操作放入会话的工作队列中。

  6. ISCSI_UEVENT_SET_PARAM:
    调用 iscsi_if_set_param 函数设置指定会话的参数。

  7. ISCSI_UEVENT_CREATE_CONNISCSI_UEVENT_DESTROY_CONNISCSI_UEVENT_STOP_CONNISCSI_UEVENT_START_CONNISCSI_UEVENT_BIND_CONNISCSI_UEVENT_SEND_PDU:
    调用 iscsi_if_transport_conn 函数处理与 iSCSI 连接相关的操作。

  8. ISCSI_UEVENT_GET_STATS:
    调用 iscsi_if_get_stats 函数获取 iSCSI 统计信息。

  9. ISCSI_UEVENT_TRANSPORT_EP_CONNECTISCSI_UEVENT_TRANSPORT_EP_POLLISCSI_UEVENT_TRANSPORT_EP_DISCONNECTISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST:
    调用 iscsi_if_transport_ep 函数处理与传输层端点相关的操作。

  10. ISCSI_UEVENT_TGT_DSCVR:
    调用 iscsi_tgt_dscvr 函数执行目标发现操作。

  11. ISCSI_UEVENT_SET_HOST_PARAM:
    调用 iscsi_set_host_param 函数设置主机参数。

  12. ISCSI_UEVENT_PATH_UPDATE:
    调用 iscsi_set_path 函数更新路径信息。

  13. ISCSI_UEVENT_SET_IFACE_PARAMS:
    调用 iscsi_set_iface_params 函数设置接口参数。

  14. ISCSI_UEVENT_PING:
    调用 iscsi_send_ping 函数执行 Ping 操作。

  15. ISCSI_UEVENT_GET_CHAP:
    调用 iscsi_get_chap 函数获取 CHAP 认证信息。

  16. ISCSI_UEVENT_DELETE_CHAP:
    调用 iscsi_delete_chap 函数删除 CHAP 认证信息。

  17. ISCSI_UEVENT_SET_FLASHNODE_PARAMS:
    调用 iscsi_set_flashnode_param 函数设置闪存节点参数。

  18. ISCSI_UEVENT_NEW_FLASHNODE:
    调用 iscsi_new_flashnode 函数创建新的闪存节点。

  19. ISCSI_UEVENT_DEL_FLASHNODE:
    调用 iscsi_del_flashnode 函数删除指定的闪存节点。

  20. ISCSI_UEVENT_LOGIN_FLASHNODE:
    调用 iscsi_login_flashnode 函数执行闪存节点登录操作。

  21. ISCSI_UEVENT_LOGOUT_FLASHNODE:
    调用 iscsi_logout_flashnode 函数执行闪存节点登出操作。

  22. ISCSI_UEVENT_LOGOUT_FLASHNODE_SID:
    调用 iscsi_logout_flashnode_sid 函数根据会话 ID 执行闪存节点登出操作。

  23. ISCSI_UEVENT_SET_CHAP:
    调用 iscsi_set_chap 函数设置 CHAP 认证信息。

  24. ISCSI_UEVENT_GET_HOST_STATS:
    调用 `iscsi_get_host

_stats` 函数获取主机统计信息。

如果消息类型未在上述处理步骤中列出,则返回错误码 -ENOSYS,表示不支持的消息类型。

函数执行后会减少传输层的引用计数。每个处理分支都会返回一个错误码,指示操作的成功或失败。这个函数的主要目的是根据不同的消息类型,调用相应的处理函数来执行 iSCSI 相关的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值