summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGavin Shan <gwshan@linux.vnet.ibm.com>2015-10-06 05:52:39 +0300
committerJoel Stanley <joel@jms.id.au>2015-10-06 06:50:04 +0300
commitd446a3dc5564e8f3ef3366c8a87a4a6cc61ec897 (patch)
tree0e8e869c0b79bb6da581fceab0dbf2513fa0bbed
parent95b4c4d49ca7eac358e533ae8a66e434a1c76e17 (diff)
downloadlinux-d446a3dc5564e8f3ef3366c8a87a4a6cc61ec897.tar.xz
net/ncsi: Manage NCSI device
There are multiple packages and channels connecting to NCSI enabled interface. The available packages and channels are probed with NCSI packets when NCSI device is started. Among the available packages and channels, only one channel should be selected to be active to provide service. When the active channel enters to failure state, we need choose another channel as active one to do failover. This implements the state machine to support above tasks. Also, the public interfaces are exposed for NIC driver to manage NCSI device. Signed-off-by: Gavin Shan <gwshan@linux.vnet.ibm.com>
-rw-r--r--include/net/ncsi.h38
-rw-r--r--net/ncsi/internal.h1
-rw-r--r--net/ncsi/ncsi-aen.c9
-rw-r--r--net/ncsi/ncsi-manage.c539
4 files changed, 584 insertions, 3 deletions
diff --git a/include/net/ncsi.h b/include/net/ncsi.h
index 2eecfbd16e84..6041ca3d821b 100644
--- a/include/net/ncsi.h
+++ b/include/net/ncsi.h
@@ -3,6 +3,17 @@
#include <uapi/linux/ncsi.h>
+/*
+ * The NCSI device states seen from external. More NCSI device states are
+ * only visible internally (in net/ncsi/internal.h). When the NCSI device
+ * is registered, it's in ncsi_dev_state_registered state. The state
+ * ncsi_dev_state_start is used to drive to choose active package and
+ * channel. After that, its state is changed to ncsi_dev_state_functional.
+ *
+ * The state ncsi_dev_state_stop helps to shut down the currently active
+ * package and channel while ncsi_dev_state_config helps to reconfigure
+ * them.
+ */
enum {
ncsi_dev_state_registered = 0x0000,
ncsi_dev_state_functional = 0x0100,
@@ -18,4 +29,31 @@ struct ncsi_dev {
void (*nd_handler)(struct ncsi_dev *ndev);
};
+#ifdef CONFIG_NET_NCSI
+struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
+ void (*notifier)(struct ncsi_dev *nd));
+int ncsi_start_dev(struct ncsi_dev *nd);
+int ncsi_stop_dev(struct ncsi_dev *nd);
+void ncsi_unregister_dev(struct ncsi_dev *nd);
+#else /* !CONFIG_NET_NCSI */
+static inline struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
+ void (*notifier)(struct ncsi_dev *nd))
+{
+ return NULL;
+}
+
+static inline int ncsi_start_dev(struct ncsi_dev *nd)
+{
+ return -ENOTTY;
+}
+
+static inline int ncsi_stop_dev(struct ncsi_dev *nd)
+{
+ return -ENOTTY;
+}
+
+void inline ncsi_unregister_dev(struct ncsi_dev *nd)
+{
+}
+#endif /* CONFIG_NET_NCSI */
#endif /* __NET_NCSI_H */
diff --git a/net/ncsi/internal.h b/net/ncsi/internal.h
index 92627177edb6..02cab912ce85 100644
--- a/net/ncsi/internal.h
+++ b/net/ncsi/internal.h
@@ -149,6 +149,7 @@ void ncsi_find_package_and_channel(struct ncsi_dev_priv *ndp,
struct ncsi_req *ncsi_alloc_req(struct ncsi_dev_priv *ndp);
void ncsi_free_req(struct ncsi_req *nr, bool check, bool timeout);
struct ncsi_dev *ncsi_find_dev(struct net_device *dev);
+int ncsi_config_dev(struct ncsi_dev *nd);
/* Packet handlers */
int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca);
diff --git a/net/ncsi/ncsi-aen.c b/net/ncsi/ncsi-aen.c
index f5f4d2f10dff..89433bc197e7 100644
--- a/net/ncsi/ncsi-aen.c
+++ b/net/ncsi/ncsi-aen.c
@@ -60,6 +60,7 @@ static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h,
static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp,
struct ncsi_aen_pkt_hdr *h)
{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
struct ncsi_aen_lsc_pkt *lsc;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
@@ -88,7 +89,7 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp,
* we have to choose another channel to be active one.
*/
ndp->ndp_flags |= NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
- /* FIXME: Stop active channel and choose another one */
+ ncsi_stop_dev(nd);
return 0;
}
@@ -96,6 +97,7 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp,
static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp,
struct ncsi_aen_pkt_hdr *h)
{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
struct ncsi_channel *nc;
int ret;
@@ -113,7 +115,7 @@ static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp,
ndp->ndp_active_channel != nc)
return 0;
- /* FIXME: Reconfigure active channel */
+ ncsi_config_dev(nd);
return 0;
}
@@ -121,6 +123,7 @@ static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp,
static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp,
struct ncsi_aen_pkt_hdr *h)
{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
struct ncsi_channel *nc;
struct ncsi_channel_mode *ncm;
struct ncsi_aen_hncdsc_pkt *hncdsc;
@@ -149,7 +152,7 @@ static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp,
* is down on the active channel.
*/
ndp->ndp_flags |= NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
- /* FIXME: Stop and choose another channel as active one */
+ ncsi_stop_dev(nd);
return 0;
}
diff --git a/net/ncsi/ncsi-manage.c b/net/ncsi/ncsi-manage.c
index 765217c4e01e..5d66b6fe5329 100644
--- a/net/ncsi/ncsi-manage.c
+++ b/net/ncsi/ncsi-manage.c
@@ -18,6 +18,7 @@
#include <net/net_namespace.h>
#include <net/sock.h>
+#include "ncsi-pkt.h"
#include "internal.h"
LIST_HEAD(ncsi_dev_list);
@@ -350,6 +351,8 @@ void ncsi_free_req(struct ncsi_req *nr, bool check, bool timeout)
nr->nr_used = false;
spin_unlock(&ndp->ndp_req_lock);
+ if (check && cmd && atomic_dec_return(&ndp->ndp_pending_reqs) == 0)
+ schedule_work(&ndp->ndp_work);
/* Release command and response */
consume_skb(cmd);
consume_skb(rsp);
@@ -366,3 +369,539 @@ struct ncsi_dev *ncsi_find_dev(struct net_device *dev)
return NULL;
}
+
+static int ncsi_select_active_channel(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_package *np;
+ struct ncsi_channel *nc;
+
+ /* For now, we simply choose the first valid channel as active one.
+ * There might be more factors, like the channel's capacity, can
+ * be considered to pick the active channel in future.
+ */
+ NCSI_FOR_EACH_PACKAGE(ndp, np) {
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ ndp->ndp_active_package = np;
+ ndp->ndp_active_channel = nc;
+ return 0;
+ }
+ }
+
+ return -ENXIO;
+}
+
+static void ncsi_dev_config(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct net_device *dev = nd->nd_dev;
+ struct ncsi_package *np = ndp->ndp_active_package;
+ struct ncsi_channel *nc = ndp->ndp_active_channel;
+ struct ncsi_cmd_arg nca;
+ unsigned char index;
+ int ret;
+
+ nca.nca_ndp = ndp;
+ nca.nca_nlh = NULL;
+
+ /* When we're reconfiguring the active channel, the active package
+ * should be selected and the old setting on the active channel
+ * should be cleared.
+ */
+ switch (nd->nd_state) {
+ case ncsi_dev_state_config:
+ case ncsi_dev_state_config_sp:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ /* Select the specific package */
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_bytes[0] = 1;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = 0x1f;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_config_cis;
+ break;
+ case ncsi_dev_state_config_cis:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ /* Clear initial state */
+ nca.nca_type = NCSI_PKT_CMD_CIS;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = nc->nc_id;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_config_sma;
+ break;
+ case ncsi_dev_state_config_sma:
+ case ncsi_dev_state_config_ebf:
+ case ncsi_dev_state_config_ecnt:
+ case ncsi_dev_state_config_ec:
+ case ncsi_dev_state_config_gls:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ nca.nca_package = np->np_id;
+ nca.nca_channel = nc->nc_id;
+
+ /* Use first entry in unicast filter table. Note that
+ * the MAC filter table starts from entry 1 instead of
+ * 0.
+ */
+ if (nd->nd_state == ncsi_dev_state_config_sma) {
+ nca.nca_type = NCSI_PKT_CMD_SMA;
+ for (index = 0; index < 6; index++)
+ nca.nca_bytes[index] = dev->dev_addr[index];
+ nca.nca_bytes[6] = 0x1;
+ nca.nca_bytes[7] = 0x1;
+ nd->nd_state = ncsi_dev_state_config_ebf;
+ } else if (nd->nd_state == ncsi_dev_state_config_ebf) {
+ nca.nca_type = NCSI_PKT_CMD_EBF;
+ nca.nca_dwords[0] = nc->nc_caps[NCSI_CAP_BC].ncc_cap;
+ nd->nd_state = ncsi_dev_state_config_ecnt;
+ } else if (nd->nd_state == ncsi_dev_state_config_ecnt) {
+ nca.nca_type = NCSI_PKT_CMD_ECNT;
+ nd->nd_state = ncsi_dev_state_config_ec;
+ } else if (nd->nd_state == ncsi_dev_state_config_ec) {
+ nca.nca_type = NCSI_PKT_CMD_EC;
+ nd->nd_state = ncsi_dev_state_config_gls;
+ } else if (nd->nd_state == ncsi_dev_state_config_gls) {
+ nca.nca_type = NCSI_PKT_CMD_GLS;
+ nd->nd_state = ncsi_dev_state_config_done;
+ }
+
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ break;
+ case ncsi_dev_state_config_done:
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ if (nc->nc_modes[NCSI_MODE_LINK].ncm_data[2] & 0x1)
+ nd->nd_link_up = 1;
+
+ if (!(ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE))
+ nd->nd_handler(nd);
+ ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
+
+ break;
+ default:
+ pr_debug("%s: Unrecognized NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ return;
+ }
+
+ return;
+
+error:
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
+ nd->nd_handler(nd);
+}
+
+static void ncsi_dev_start(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct ncsi_package *np;
+ struct ncsi_channel *nc;
+ struct ncsi_cmd_arg nca;
+ unsigned char index;
+ int ret;
+
+ nca.nca_ndp = ndp;
+ nca.nca_nlh = NULL;
+ switch (nd->nd_state) {
+ case ncsi_dev_state_start:
+ nd->nd_state = ncsi_dev_state_start_deselect;
+ /* Fall through */
+ case ncsi_dev_state_start_deselect:
+ atomic_set(&ndp->ndp_pending_reqs, 8);
+
+ /* Deselect all possible packages */
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ nca.nca_channel = 0x1f;
+ for (index = 0; index < 8; index++) {
+ nca.nca_package = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ nd->nd_state = ncsi_dev_state_start_package;
+ break;
+ case ncsi_dev_state_start_package:
+ atomic_set(&ndp->ndp_pending_reqs, 16);
+
+ /* Select all possible packages */
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_bytes[0] = 1;
+ nca.nca_channel = 0x1f;
+ for (index = 0; index < 8; index++) {
+ nca.nca_package = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ /* Disable all possible packages */
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ for (index = 0; index < 8; index++) {
+ nca.nca_package = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ nd->nd_state = ncsi_dev_state_start_channel;
+ break;
+ case ncsi_dev_state_start_channel:
+ /* The available packages should have been detected. To
+ * iterate every package to probe its channels.
+ */
+ if (!ndp->ndp_active_package) {
+ ndp->ndp_active_package = list_first_or_null_rcu(
+ &ndp->ndp_packages, struct ncsi_package,
+ np_node);
+ if (!ndp->ndp_active_package)
+ goto error;
+ } else {
+ if (list_is_last(&ndp->ndp_active_package->np_node,
+ &ndp->ndp_packages)) {
+ nd->nd_state = ncsi_dev_state_start_active;
+ goto choose_active_channel;
+ }
+
+ ndp->ndp_active_package = list_entry_rcu(
+ ndp->ndp_active_package->np_node.next,
+ struct ncsi_package, np_node);
+ }
+ /* Fall through */
+ case ncsi_dev_state_start_sp:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ /* Select the specific package */
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_bytes[0] = 1;
+ nca.nca_package = ndp->ndp_active_package->np_id;
+ nca.nca_channel = 0x1f;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_start_cis;
+ break;
+ case ncsi_dev_state_start_cis:
+ atomic_set(&ndp->ndp_pending_reqs, 0x20);
+
+ /* Clear initial state */
+ nca.nca_type = NCSI_PKT_CMD_CIS;
+ nca.nca_package = ndp->ndp_active_package->np_id;
+ for (index = 0; index < 0x20; index++) {
+ nca.nca_channel = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ nd->nd_state = ncsi_dev_state_start_gvi;
+ break;
+ case ncsi_dev_state_start_gvi:
+ case ncsi_dev_state_start_gc:
+ /* The available channels of the active package should have
+ * been populated.
+ */
+ np = ndp->ndp_active_package;
+ atomic_set(&ndp->ndp_pending_reqs,
+ atomic_read(&np->np_channel_num));
+
+ /* Get version information or get capacity */
+ if (nd->nd_state == ncsi_dev_state_start_gvi)
+ nca.nca_type = NCSI_PKT_CMD_GVI;
+ else
+ nca.nca_type = NCSI_PKT_CMD_GC;
+
+ nca.nca_package = np->np_id;
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ nca.nca_channel = nc->nc_id;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ if (nd->nd_state == ncsi_dev_state_start_gvi)
+ nd->nd_state = ncsi_dev_state_start_gc;
+ else
+ nd->nd_state = ncsi_dev_state_start_dp;
+ break;
+ case ncsi_dev_state_start_dp:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ /* Deselect the active package */
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ nca.nca_package = ndp->ndp_active_package->np_id;
+ nca.nca_channel = 0x1f;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_start_channel;
+ break;
+ case ncsi_dev_state_start_active:
+choose_active_channel:
+ /* All packages and channels should have been populated. Also,
+ * the information for all channels should have been retrieved.
+ */
+ ndp->ndp_active_package = NULL;
+ ncsi_select_active_channel(ndp);
+ if (!ndp->ndp_active_package ||
+ !ndp->ndp_active_channel)
+ goto error;
+
+ /* To configure the active channel */
+ nd->nd_state = ncsi_dev_state_config_sma;
+ ncsi_dev_config(ndp);
+ default:
+ pr_debug("%s: Unrecognized NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ }
+
+ return;
+
+error:
+ ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ nd->nd_handler(nd);
+}
+
+static void ncsi_dev_stop(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct ncsi_package *np, *tmp;
+ struct ncsi_channel *nc;
+ struct ncsi_cmd_arg nca;
+ int ret;
+
+ nca.nca_ndp = ndp;
+ nca.nca_nlh = NULL;
+ switch (nd->nd_state) {
+ case ncsi_dev_state_stop:
+ /* If there're no active channel, we're done */
+ if (!ndp->ndp_active_channel) {
+ nd->nd_state = ncsi_dev_state_stop_done;
+ goto done;
+ }
+
+ nd->nd_state = ncsi_dev_state_stop_select;
+ /* Fall through */
+ case ncsi_dev_state_stop_select:
+ case ncsi_dev_state_stop_dcnt:
+ case ncsi_dev_state_stop_dc:
+ case ncsi_dev_state_stop_deselect:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ np = ndp->ndp_active_package;
+ nc = ndp->ndp_active_channel;
+ nca.nca_package = np->np_id;
+ if (nd->nd_state == ncsi_dev_state_stop_select) {
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_channel = 0x1f;
+ nca.nca_bytes[0] = 1;
+ nd->nd_state = ncsi_dev_state_stop_dcnt;
+ } else if (nd->nd_state == ncsi_dev_state_stop_dcnt) {
+ nca.nca_type = NCSI_PKT_CMD_DCNT;
+ nca.nca_channel = nc->nc_id;
+ nd->nd_state = ncsi_dev_state_stop_dc;
+ } else if (nd->nd_state == ncsi_dev_state_stop_dc) {
+ nca.nca_type = NCSI_PKT_CMD_DC;
+ nca.nca_channel = nc->nc_id;
+ nca.nca_bytes[0] = 1;
+ nd->nd_state = ncsi_dev_state_stop_deselect;
+ } else if (nd->nd_state == ncsi_dev_state_stop_deselect) {
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ nca.nca_channel = 0x1f;
+ nd->nd_state = ncsi_dev_state_stop_done;
+ }
+
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret) {
+ nd->nd_state = ncsi_dev_state_stop_done;
+ goto done;
+ }
+
+ break;
+ case ncsi_dev_state_stop_done:
+done:
+ spin_lock(&ndp->ndp_package_lock);
+ list_for_each_entry_safe(np, tmp, &ndp->ndp_packages, np_node)
+ ncsi_release_package(np);
+ spin_unlock(&ndp->ndp_package_lock);
+
+ if (!(ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE)) {
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ nd->nd_handler(nd);
+ } else {
+ nd->nd_state = ncsi_dev_state_start;
+ ncsi_dev_start(ndp);
+ }
+
+ break;
+ default:
+ pr_warn("%s: Unsupported NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ }
+}
+
+static void ncsi_dev_work(struct work_struct *work)
+{
+ struct ncsi_dev_priv *ndp = container_of(work, struct ncsi_dev_priv,
+ ndp_work);
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+
+ switch (nd->nd_state & ncsi_dev_state_major) {
+ case ncsi_dev_state_start:
+ ncsi_dev_start(ndp);
+ break;
+ case ncsi_dev_state_stop:
+ ncsi_dev_stop(ndp);
+ break;
+ case ncsi_dev_state_config:
+ ncsi_dev_config(ndp);
+ break;
+ default:
+ pr_warn("%s: Unsupported NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ }
+}
+
+static void ncsi_req_timeout(unsigned long data)
+{
+ struct ncsi_req *nr = (struct ncsi_req *)data;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+
+ /* If the request already had associated response,
+ * let the response handler to release it.
+ */
+ spin_lock(&ndp->ndp_req_lock);
+ nr->nr_timer_enabled = false;
+ if (nr->nr_rsp || !nr->nr_cmd) {
+ spin_unlock(&ndp->ndp_req_lock);
+ return;
+ }
+ spin_unlock(&ndp->ndp_req_lock);
+
+ /* Release the request */
+ ncsi_free_req(nr, true, true);
+}
+
+struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
+ void (*handler)(struct ncsi_dev *ndev))
+{
+ struct ncsi_dev_priv *ndp;
+ struct ncsi_dev *nd;
+ int idx;
+
+ /* Check if the device has been registered or not */
+ nd = ncsi_find_dev(dev);
+ if (nd)
+ return nd;
+
+ /* Create NCSI device */
+ ndp = kzalloc(sizeof(*ndp), GFP_ATOMIC);
+ if (!ndp) {
+ pr_warn("%s: Out of memory !\n", __func__);
+ return NULL;
+ }
+
+ nd = &ndp->ndp_ndev;
+ nd->nd_state = ncsi_dev_state_registered;
+ nd->nd_dev = dev;
+ nd->nd_handler = handler;
+
+ /* Initialize private NCSI device */
+ spin_lock_init(&ndp->ndp_package_lock);
+ INIT_LIST_HEAD(&ndp->ndp_packages);
+ INIT_WORK(&ndp->ndp_work, ncsi_dev_work);
+ spin_lock_init(&ndp->ndp_req_lock);
+ atomic_set(&ndp->ndp_last_req_idx, 0);
+ for (idx = 0; idx < 256; idx++) {
+ ndp->ndp_reqs[idx].nr_id = idx;
+ ndp->ndp_reqs[idx].nr_ndp = ndp;
+ setup_timer(&ndp->ndp_reqs[idx].nr_timer, ncsi_req_timeout,
+ (unsigned long)&ndp->ndp_reqs[idx]);
+ }
+
+ spin_lock(&ncsi_dev_lock);
+ list_add_tail_rcu(&ndp->ndp_node, &ncsi_dev_list);
+ spin_unlock(&ncsi_dev_lock);
+
+ /* Register NCSI packet receiption handler */
+ ndp->ndp_ptype.type = cpu_to_be16(ETH_P_NCSI);
+ ndp->ndp_ptype.func = ncsi_rcv_rsp;
+ ndp->ndp_ptype.dev = dev;
+ dev_add_pack(&ndp->ndp_ptype);
+
+ return nd;
+}
+EXPORT_SYMBOL_GPL(ncsi_register_dev);
+
+int ncsi_start_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+
+ if (nd->nd_state != ncsi_dev_state_registered &&
+ nd->nd_state != ncsi_dev_state_functional)
+ return -ENOTTY;
+
+ nd->nd_state = ncsi_dev_state_start;
+ schedule_work(&ndp->ndp_work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ncsi_start_dev);
+
+int ncsi_config_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+
+ if (nd->nd_state != ncsi_dev_state_functional)
+ return -ENOTTY;
+
+ nd->nd_state = ncsi_dev_state_config;
+ schedule_work(&ndp->ndp_work);
+
+ return 0;
+}
+
+int ncsi_stop_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+
+ if (nd->nd_state != ncsi_dev_state_functional)
+ return -ENOTTY;
+
+ nd->nd_state = ncsi_dev_state_stop;
+ schedule_work(&ndp->ndp_work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ncsi_stop_dev);
+
+void ncsi_unregister_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+ struct ncsi_package *np, *tmp;
+
+ dev_remove_pack(&ndp->ndp_ptype);
+
+ spin_lock(&ndp->ndp_package_lock);
+ list_for_each_entry_safe(np, tmp, &ndp->ndp_packages, np_node)
+ ncsi_release_package(np);
+ spin_unlock(&ndp->ndp_package_lock);
+}
+EXPORT_SYMBOL_GPL(ncsi_unregister_dev);