}

static void stop_conn_work_fn(struct work_struct *work)
{
	struct iscsi_cls_conn *conn, *tmp;
	unsigned long flags;
	LIST_HEAD(recovery_list);

	spin_lock_irqsave(&connlock, flags);
	if (list_empty(&connlist_err)) {
		spin_unlock_irqrestore(&connlock, flags);
		return;
	}
	list_splice_init(&connlist_err, &recovery_list);
	spin_unlock_irqrestore(&connlock, flags);

	list_for_each_entry_safe(conn, tmp, &recovery_list, conn_list_err) {
		uint32_t sid = iscsi_conn_get_sid(conn);
		struct iscsi_cls_session *session;

		session = iscsi_session_lookup(sid);
		if (session) {
			if (system_state != SYSTEM_RUNNING) {
				session->recovery_tmo = 0;
				iscsi_if_stop_conn(conn, STOP_CONN_TERM);
			} else {
				iscsi_if_stop_conn(conn, STOP_CONN_RECOVER);
			}
		}


 * iscsi_bsg_host_dispatch - Dispatch command to LLD.
 * @job: bsg job to be processed
 */
static int iscsi_bsg_host_dispatch(struct bsg_job *job)
{
	struct Scsi_Host *shost = iscsi_job_to_shost(job);
	struct iscsi_bsg_request *req = job->request;
	struct iscsi_bsg_reply *reply = job->reply;
	struct iscsi_internal *i = to_iscsi_internal(shost->transportt);
	int cmdlen = sizeof(uint32_t);	/* start with length of msgcode */
	int ret;

	/* check if we have the msgcode value at least */
	if (job->request_len < sizeof(uint32_t)) {
		ret = -ENOMSG;
		goto fail_host_msg;
	}

	/* Validate the host command */
	switch (req->msgcode) {
	case ISCSI_BSG_HST_VENDOR:
		cmdlen += sizeof(struct iscsi_bsg_host_vendor);
		if ((shost->hostt->vendor_id == 0L) ||
		    (req->rqst_data.h_vendor.vendor_id !=
			shost->hostt->vendor_id)) {
			ret = -ESRCH;
			goto fail_host_msg;
		}
		break;
	default:
		ret = -EBADR;
		goto fail_host_msg;
	}

	/* check if we really have all the request data needed */
	if (job->request_len < cmdlen) {
		ret = -ENOMSG;
		goto fail_host_msg;
	}

	ret = i->iscsi_transport->bsg_request(job);
	if (!ret)
		return 0;

fail_host_msg:
	/* return the errno failure code as the only status */
	BUG_ON(job->reply_len < sizeof(uint32_t));
	reply->reply_payload_rcv_len = 0;
	reply->result = ret;
	job->reply_len = sizeof(uint32_t);

scsi: iscsi: Verify lengths on passthrough PDUs

Open-iSCSI sends passthrough PDUs over netlink, but the kernel should be
verifying that the provided PDU header and data lengths fall within the
netlink message to prevent accessing beyond that in memory.

Cc: stable@vger.kernel.org
Reported-by: Adam Nichols <adam@grimm-co.com>
Reviewed-by: Lee Duncan <lduncan@suse.com>
Reviewed-by: Mike Christie <michael.christie@oracle.com>
Signed-off-by: Chris Leech <cleech@redhat.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>


}
EXPORT_SYMBOL_GPL(iscsi_remove_session);

void iscsi_free_session(struct iscsi_cls_session *session)
{
	ISCSI_DBG_TRANS_SESSION(session, "Freeing session\n");


}

static int
iscsi_unicast_skb(struct sk_buff *skb, u32 portid)


	{ISCSI_PORT_STATE_UP,		"LINK UP" },
};

char *iscsi_get_port_state_name(struct Scsi_Host *shost)
{
	int i;
	char *state = "Unknown!";
	struct iscsi_cls_host *ihost = shost->shost_data;
	uint32_t port_state = ihost->port_state;

	for (i = 0; i < ARRAY_SIZE(iscsi_port_state_names); i++) {
		if (iscsi_port_state_names[i].value & port_state) {
			state = iscsi_port_state_names[i].name;
			break;
		}


/*
 * Returns the matching session to a given sid
 */
static struct iscsi_cls_session *iscsi_session_lookup(uint32_t sid)
{
	unsigned long flags;
	struct iscsi_cls_session *sess;

	spin_lock_irqsave(&sesslock, flags);
	list_for_each_entry(sess, &sesslist, sess_list) {
		if (sess->sid == sid) {
			spin_unlock_irqrestore(&sesslock, flags);
			return sess;
		}
	}


	ISCSI_DBG_TRANS_SESSION(session, "Completed unblocking SCSI target\n");
}

static void __iscsi_unblock_session(struct work_struct *work)
{
	struct iscsi_cls_session *session =
			container_of(work, struct iscsi_cls_session,
				     unblock_work);
	struct Scsi_Host *shost = iscsi_session_to_shost(session);
	struct iscsi_cls_host *ihost = shost->shost_data;
	unsigned long flags;

	ISCSI_DBG_TRANS_SESSION(session, "Unblocking session\n");
	/*
	 * The recovery and unblock work get run from the same workqueue,
	 * so try to cancel it if it was going to run after this unblock.
	 */
	cancel_delayed_work(&session->recovery_work);
	spin_lock_irqsave(&session->lock, flags);
	session->state = ISCSI_SESSION_LOGGED_IN;
	spin_unlock_irqrestore(&session->lock, flags);
	/* start IO */
	scsi_target_unblock(&session->dev, SDEV_RUNNING);
	/*
	 * Only do kernel scanning if the driver is properly hooked into
	 * the async scanning code (drivers like iscsi_tcp do login and
	 * scanning from userspace).
	 */
	if (shost->hostt->scan_finished) {
		if (scsi_queue_work(shost, &session->scan_work))
			atomic_inc(&ihost->nr_scans);


EXPORT_SYMBOL_GPL(iscsi_session_event);

static int
iscsi_if_create_session(struct iscsi_internal *priv, struct iscsi_endpoint *ep,
			struct iscsi_uevent *ev, pid_t pid,
			uint32_t initial_cmdsn,	uint16_t cmds_max,
			uint16_t queue_depth)
{
	struct iscsi_transport *transport = priv->iscsi_transport;
	struct iscsi_cls_session *session;
	struct Scsi_Host *shost;

	session = transport->create_session(ep, cmds_max, queue_depth,
					    initial_cmdsn);
	if (!session)
		return -ENOMEM;

	session->creator = pid;
	shost = iscsi_session_to_shost(session);
	ev->r.c_session_ret.host_no = shost->host_no;
	ev->r.c_session_ret.sid = session->sid;
	ISCSI_DBG_TRANS_SESSION(session,


				   session->recovery_tmo * HZ);
}

void iscsi_block_session(struct iscsi_cls_session *session)


 *  1 on success
 *  0 on failure
 */
static int iscsi_is_flashnode_conn_dev(struct device *dev, void *data)


 * iscsi interface functions
 */
static struct iscsi_internal *
iscsi_if_transport_lookup(struct iscsi_transport *tt)
{
	struct iscsi_internal *priv;
	unsigned long flags;

	spin_lock_irqsave(&iscsi_transport_lock, flags);
	list_for_each_entry(priv, &iscsi_transports, list) {
		if (tt == priv->iscsi_transport) {
			spin_unlock_irqrestore(&iscsi_transport_lock, flags);
			return priv;
		}
	}


}

static int
iscsi_if_destroy_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
{
	struct iscsi_cls_conn *conn;
	unsigned long flags;

	conn = iscsi_conn_lookup(ev->u.d_conn.sid, ev->u.d_conn.cid);
	if (!conn)
		return -EINVAL;

	spin_lock_irqsave(&connlock, flags);
	if (!list_empty(&conn->conn_list_err)) {
		spin_unlock_irqrestore(&connlock, flags);
		return -EAGAIN;
	}
	spin_unlock_irqrestore(&connlock, flags);

	ISCSI_DBG_TRANS_CONN(conn, "Destroying transport conn\n");

	mutex_lock(&conn_mutex);
	if (transport->destroy_conn)
		transport->destroy_conn(conn);
	mutex_unlock(&conn_mutex);


	return err;
}

static int iscsi_if_ep_connect(struct iscsi_transport *transport,
			       struct iscsi_uevent *ev, int msg_type)
{
	struct iscsi_endpoint *ep;
	struct sockaddr *dst_addr;
	struct Scsi_Host *shost = NULL;
	int non_blocking, err = 0;

	if (!transport->ep_connect)
		return -EINVAL;

	if (msg_type == ISCSI_UEVENT_TRANSPORT_EP_CONNECT_THROUGH_HOST) {
		shost = scsi_host_lookup(ev->u.ep_connect_through_host.host_no);
		if (!shost) {
			printk(KERN_ERR "ep connect failed. Could not find "
			       "host no %u\n",
			       ev->u.ep_connect_through_host.host_no);
			return -ENODEV;
		}
		non_blocking = ev->u.ep_connect_through_host.non_blocking;
	} else
		non_blocking = ev->u.ep_connect.non_blocking;

	dst_addr = (struct sockaddr *)((char*)ev + sizeof(*ev));
	ep = transport->ep_connect(shost, dst_addr, non_blocking);
	if (IS_ERR(ep)) {
		err = PTR_ERR(ep);
		goto release_host;
	}

	ev->r.ep_connect_ret.handle = ep->id;
release_host:
	if (shost)


	return &priv->session_cont.ac == cont;
}

static int iscsi_conn_match(struct attribute_container *cont,
			   struct device *dev)
{
	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_conn(dev);
	session = iscsi_dev_to_session(conn->dev.parent);
	shost = iscsi_session_to_shost(session);

	if (!shost->transportt)
		return 0;

	priv = to_iscsi_internal(shost->transportt);
	if (priv->conn_cont.ac.class != &iscsi_connection_class.class)
		return 0;


static LIST_HEAD(connlist_err);
static DEFINE_SPINLOCK(connlock);

static uint32_t iscsi_conn_get_sid(struct iscsi_cls_conn *conn)
{


 *
 * This can be called from a LLD or iscsi_transport.
 */
int iscsi_destroy_conn(struct iscsi_cls_conn *conn)
{
	unsigned long flags;

	spin_lock_irqsave(&connlock, flags);
	list_del(&conn->conn_list);
	list_del(&conn->conn_list_err);
	spin_unlock_irqrestore(&connlock, flags);

	transport_unregister_device(&conn->dev);
	ISCSI_DBG_TRANS_CONN(conn, "Completing conn destruction\n");


 *  %NULL on failure
 */
struct device *
iscsi_find_flashnode_conn(struct iscsi_bus_flash_session *fnode_sess)
{


	put_device(parent);
}

static int iscsi_is_conn_dev(const struct device *dev)


}
EXPORT_SYMBOL_GPL(iscsi_dbg_trace);

static __init int iscsi_transport_init(void)
{
	int err;
	struct netlink_kernel_cfg cfg = {
		.groups	= 1,
		.input	= iscsi_if_rx,
	};
	printk(KERN_INFO "Loading iSCSI transport class v%s.\n",
		ISCSI_TRANSPORT_VERSION);

	atomic_set(&iscsi_session_nr, 0);

	err = class_register(&iscsi_transport_class);
	if (err)
		return err;

	err = class_register(&iscsi_endpoint_class);
	if (err)
		goto unregister_transport_class;

	err = class_register(&iscsi_iface_class);
	if (err)
		goto unregister_endpoint_class;

	err = transport_class_register(&iscsi_host_class);
	if (err)
		goto unregister_iface_class;

	err = transport_class_register(&iscsi_connection_class);
	if (err)
		goto unregister_host_class;

	err = transport_class_register(&iscsi_session_class);
	if (err)
		goto unregister_conn_class;

	err = bus_register(&iscsi_flashnode_bus);
	if (err)
		goto unregister_session_class;

	nls = netlink_kernel_create(&init_net, NETLINK_ISCSI, &cfg);
	if (!nls) {
		err = -ENOBUFS;
		goto unregister_flashnode_bus;
	}

	iscsi_eh_timer_workq = alloc_workqueue("%s",
			WQ_SYSFS | __WQ_LEGACY | WQ_MEM_RECLAIM | WQ_UNBOUND,
			1, "iscsi_eh");
	if (!iscsi_eh_timer_workq) {
		err = -ENOMEM;
		goto release_nls;
	}

	iscsi_destroy_workq = alloc_workqueue("%s",
			WQ_SYSFS | __WQ_LEGACY | WQ_MEM_RECLAIM | WQ_UNBOUND,
			1, "iscsi_destroy");
	if (!iscsi_destroy_workq) {
		err = -ENOMEM;
		goto destroy_wq;
	}

	return 0;

destroy_wq:
	destroy_workqueue(iscsi_eh_timer_workq);
release_nls:
	netlink_kernel_release(nls);
unregister_flashnode_bus:
	bus_unregister(&iscsi_flashnode_bus);
unregister_session_class:
	transport_class_unregister(&iscsi_session_class);
unregister_conn_class:
	transport_class_unregister(&iscsi_connection_class);
unregister_host_class:
	transport_class_unregister(&iscsi_host_class);
unregister_iface_class:
	class_unregister(&iscsi_iface_class);
unregister_endpoint_class:
	class_unregister(&iscsi_endpoint_class);
unregister_transport_class:


	kfree(session);
}

int iscsi_is_session_dev(const struct device *dev)


show_transport_handle(struct device *dev, struct device_attribute *attr,
		      char *buf)
{
	struct iscsi_internal *priv = dev_to_iscsi_internal(dev);

	if (!capable(CAP_SYS_ADMIN))
		return -EACCES;
	return sysfs_emit(buf, "%llu\n",
		  (unsigned long long)iscsi_handle(priv->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,
		     int dd_size, unsigned int target_id)
{
	struct iscsi_cls_session *session;

	session = iscsi_alloc_session(shost, transport, dd_size);
	if (!session)
		return NULL;

	if (iscsi_add_session(session, target_id)) {
		iscsi_free_session(session);
		return NULL;


	NULL,
};

static umode_t iscsi_host_attr_is_visible(struct kobject *kobj,
					 struct attribute *attr, int i)
{
	struct device *cdev = container_of(kobj, struct device, kobj);
	struct Scsi_Host *shost = transport_class_to_shost(cdev);
	struct iscsi_internal *priv = to_iscsi_internal(shost->transportt);
	int param;

	if (attr == &dev_attr_host_netdev.attr)
		param = ISCSI_HOST_PARAM_NETDEV_NAME;
	else if (attr == &dev_attr_host_hwaddress.attr)
		param = ISCSI_HOST_PARAM_HWADDRESS;
	else if (attr == &dev_attr_host_ipaddress.attr)
		param = ISCSI_HOST_PARAM_IPADDRESS;
	else if (attr == &dev_attr_host_initiatorname.attr)
		param = ISCSI_HOST_PARAM_INITIATOR_NAME;
	else if (attr == &dev_attr_host_port_state.attr)
		param = ISCSI_HOST_PARAM_PORT_STATE;
	else if (attr == &dev_attr_host_port_speed.attr)
		param = ISCSI_HOST_PARAM_PORT_SPEED;
	else {
		WARN_ONCE(1, "Invalid host attr");
		return 0;
	}


 *
 * Mark a session as ready to accept IO.
 */
void iscsi_unblock_session(struct iscsi_cls_session *session)
{
	queue_work(iscsi_eh_timer_workq, &session->unblock_work);
	/*
	 * Blocking the session can be done from any context so we only
	 * queue the block work. Make sure the unblock work has completed
	 * because it flushes/cancels the other works and updates the state.


	return err;
}

static int iscsi_new_flashnode(struct iscsi_transport *transport,
			       struct iscsi_uevent *ev, uint32_t len)
{
	char *data = (char *)ev + sizeof(*ev);
	struct Scsi_Host *shost;
	int index;
	int err = 0;

	if (!transport->new_flashnode) {
		err = -ENOSYS;
		goto exit_new_fnode;
	}

	shost = scsi_host_lookup(ev->u.new_flashnode.host_no);
	if (!shost) {
		pr_err("%s could not find host no %u\n",
		       __func__, ev->u.new_flashnode.host_no);
		err = -ENODEV;
		goto put_host;
	}

	index = transport->new_flashnode(shost, data, len);

	if (index >= 0)
		ev->r.new_flashnode_ret.flashnode_idx = index;
	else
		err = -EIO;

put_host:
	scsi_host_put(shost);


void iscsi_destroy_iface(struct iscsi_iface *iface)
{
	sysfs_remove_group(&iface->dev.kobj, &iscsi_iface_group);
	device_unregister(&iface->dev);


}

static int
iscsi_set_path(struct iscsi_transport *transport, struct iscsi_uevent *ev)
{
	struct Scsi_Host *shost;
	struct iscsi_path *params;
	int err;

	if (!transport->set_path)
		return -ENOSYS;

	shost = scsi_host_lookup(ev->u.set_path.host_no);
	if (!shost) {
		printk(KERN_ERR "set path could not find host no %u\n",
		       ev->u.set_path.host_no);
		return -ENODEV;
	}

	params = (struct iscsi_path *)((char *)ev + sizeof(*ev));
	err = transport->set_path(shost, params);


	return iscsi_destroy_conn(iscsi_dev_to_conn(dev));
}

void iscsi_remove_session(struct iscsi_cls_session *session)
{
	unsigned long flags;
	int err;

	ISCSI_DBG_TRANS_SESSION(session, "Removing session\n");

	spin_lock_irqsave(&sesslock, flags);
	if (!list_empty(&session->sess_list))
		list_del(&session->sess_list);
	spin_unlock_irqrestore(&sesslock, flags);

	flush_work(&session->block_work);
	flush_work(&session->unblock_work);
	cancel_delayed_work_sync(&session->recovery_work);
	/*
	 * If we are blocked let commands flow again. The lld or iscsi
	 * layer should set up the queuecommand to fail commands.
	 * We assume that LLD will not be calling block/unblock while
	 * removing the session.
	 */
	spin_lock_irqsave(&session->lock, flags);
	session->state = ISCSI_SESSION_FREE;
	spin_unlock_irqrestore(&session->lock, flags);

	scsi_target_unblock(&session->dev, SDEV_TRANSPORT_OFFLINE);
	/* flush running scans then delete devices */
	flush_work(&session->scan_work);
	/* flush running unbind operations */
	flush_work(&session->unbind_work);
	__iscsi_unbind_session(&session->unbind_work);

	/* hw iscsi may not have removed all connections from session */
	err = device_for_each_child(&session->dev, NULL,
				    iscsi_iter_destroy_conn_fn);
	if (err)
		iscsi_cls_session_printk(KERN_ERR, session,
					 "Could not delete all connections "
					 "for session. Error %d.\n", err);

	transport_unregister_device(&session->dev);


char *iscsi_get_ipaddress_state_name(enum iscsi_ipaddress_state port_state)
{
	int i;
	char *state = NULL;

	for (i = 0; i < ARRAY_SIZE(iscsi_ipaddress_state_names); i++) {
		if (iscsi_ipaddress_state_names[i].value == port_state) {
			state = iscsi_ipaddress_state_names[i].name;
			break;
		}
	}
	return state;


	return err;
}

static int iscsi_if_ep_disconnect(struct iscsi_transport *transport,
				  u64 ep_handle)
{
	struct iscsi_cls_conn *conn;
	struct iscsi_endpoint *ep;

	if (!transport->ep_disconnect)
		return -EINVAL;

	ep = iscsi_lookup_endpoint(ep_handle);
	if (!ep)
		return -EINVAL;
	conn = ep->conn;
	if (conn) {
		mutex_lock(&conn->ep_mutex);
		conn->ep = NULL;
		mutex_unlock(&conn->ep_mutex);
	}


/*
 * Returns the matching connection to a given sid / cid tuple
 */
static struct iscsi_cls_conn *iscsi_conn_lookup(uint32_t sid, uint32_t cid)
{
	unsigned long flags;
	struct iscsi_cls_conn *conn;

	spin_lock_irqsave(&connlock, flags);
	list_for_each_entry(conn, &connlist, conn_list) {
		if ((conn->cid == cid) && (iscsi_conn_get_sid(conn) == sid)) {
			spin_unlock_irqrestore(&connlock, flags);
			return conn;
		}
	}


}
EXPORT_SYMBOL_GPL(iscsi_is_session_online);

static void iscsi_session_release(struct device *dev)
{
	struct iscsi_cls_session *session = iscsi_dev_to_session(dev);
	struct Scsi_Host *shost;

	shost = iscsi_session_to_shost(session);
	scsi_host_put(shost);


	ISCSI_DBG_TRANS_SESSION(session, "Completed target removal\n");
}

static void __iscsi_destroy_session(struct work_struct *work)
{
	struct iscsi_cls_session *session =
		container_of(work, struct iscsi_cls_session, destroy_work);


 *  %NULL on failure
 */
static struct iscsi_bus_flash_session *
iscsi_get_flashnode_by_index(struct Scsi_Host *shost, uint32_t idx)
{
	struct iscsi_bus_flash_session *fnode_sess = NULL;
	struct device *dev;

	dev = device_find_child(&shost->shost_gendev, &idx,
				flashnode_match_index);
	if (dev)
		fnode_sess = iscsi_dev_to_flash_session(dev);


}
EXPORT_SYMBOL_GPL(iscsi_get_port_state_name);

static int iscsi_session_match(struct attribute_container *cont,
			   struct device *dev)
{
	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_session(dev);
	shost = iscsi_session_to_shost(session);
	if (!shost->transportt)
		return 0;

	priv = to_iscsi_internal(shost->transportt);
	if (priv->session_cont.ac.class != &iscsi_session_class.class)
		return 0;


show_ep_handle(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct iscsi_endpoint *ep = iscsi_dev_to_endpoint(dev);
	return sysfs_emit(buf, "%llu\n", (unsigned long long) ep->id);
}


iscsi_create_endpoint(int dd_size)
{
	struct device *dev;
	struct iscsi_endpoint *ep;
	uint64_t id;
	int err;

	for (id = 1; id < ISCSI_MAX_EPID; id++) {
		dev = class_find_device(&iscsi_endpoint_class, NULL, &id,
					iscsi_match_epid);
		if (!dev)
			break;
		else
			put_device(dev);
	}
	if (id == ISCSI_MAX_EPID) {
		printk(KERN_ERR "Too many connections. Max supported %u\n",
		       ISCSI_MAX_EPID - 1);
		return NULL;
	}

	ep = kzalloc(sizeof(*ep) + dd_size, GFP_KERNEL);
	if (!ep)
		return NULL;

	ep->id = id;
	ep->dev.class = &iscsi_endpoint_class;
	dev_set_name(&ep->dev, "ep-%llu", (unsigned long long) id);
	err = device_register(&ep->dev);
        if (err)
                goto free_ep;

	err = sysfs_create_group(&ep->dev.kobj, &iscsi_endpoint_group);
	if (err)
		goto unregister_dev;

	if (dd_size)
		ep->dd_data = &ep[1];
	return ep;

unregister_dev:
	device_unregister(&ep->dev);
	return NULL;

free_ep:
	kfree(ep);
	return NULL;
}


	return err;
}

static int iscsi_set_chap(struct iscsi_transport *transport,
			  struct iscsi_uevent *ev, uint32_t len)
{
	char *data = (char *)ev + sizeof(*ev);
	struct Scsi_Host *shost;
	int err = 0;

	if (!transport->set_chap)
		return -ENOSYS;

	shost = scsi_host_lookup(ev->u.set_path.host_no);
	if (!shost) {
		pr_err("%s could not find host no %u\n",
		       __func__, ev->u.set_path.host_no);
		return -ENODEV;
	}

	err = transport->set_chap(shost, data, len);


}

struct scsi_transport_template *
iscsi_register_transport(struct iscsi_transport *tt)
{
	struct iscsi_internal *priv;
	unsigned long flags;
	int err;

	BUG_ON(!tt);

	priv = iscsi_if_transport_lookup(tt);
	if (priv)
		return NULL;

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return NULL;
	INIT_LIST_HEAD(&priv->list);
	priv->iscsi_transport = tt;
	priv->t.user_scan = iscsi_user_scan;
	priv->t.create_work_queue = 1;

	priv->dev.class = &iscsi_transport_class;
	dev_set_name(&priv->dev, "%s", tt->name);
	err = device_register(&priv->dev);
	if (err)
		goto free_priv;

	err = sysfs_create_group(&priv->dev.kobj, &iscsi_transport_group);
	if (err)
		goto unregister_dev;

	/* host parameters */
	priv->t.host_attrs.ac.class = &iscsi_host_class.class;
	priv->t.host_attrs.ac.match = iscsi_host_match;
	priv->t.host_attrs.ac.grp = &iscsi_host_group;
	priv->t.host_size = sizeof(struct iscsi_cls_host);
	transport_container_register(&priv->t.host_attrs);

	/* connection parameters */
	priv->conn_cont.ac.class = &iscsi_connection_class.class;
	priv->conn_cont.ac.match = iscsi_conn_match;
	priv->conn_cont.ac.grp = &iscsi_conn_group;
	transport_container_register(&priv->conn_cont);

	/* session parameters */
	priv->session_cont.ac.class = &iscsi_session_class.class;
	priv->session_cont.ac.match = iscsi_session_match;
	priv->session_cont.ac.grp = &iscsi_session_group;
	transport_container_register(&priv->session_cont);

	spin_lock_irqsave(&iscsi_transport_lock, flags);
	list_add(&priv->list, &iscsi_transports);
	spin_unlock_irqrestore(&iscsi_transport_lock, flags);

	printk(KERN_NOTICE "iscsi: registered transport (%s)\n", tt->name);
	return &priv->t;

unregister_dev:
	device_unregister(&priv->dev);
	return NULL;
free_priv:


	enum scsi_scan_mode rescan;
};

static int iscsi_user_scan_session(struct device *dev, void *data)
{
	struct iscsi_scan_data *scan_data = data;
	struct iscsi_cls_session *session;
	struct Scsi_Host *shost;
	struct iscsi_cls_host *ihost;
	unsigned long flags;
	unsigned int id;

	if (!iscsi_is_session_dev(dev))
		return 0;

	session = iscsi_dev_to_session(dev);

	ISCSI_DBG_TRANS_SESSION(session, "Scanning session\n");

	shost = iscsi_session_to_shost(session);
	ihost = shost->shost_data;

	mutex_lock(&ihost->mutex);
	spin_lock_irqsave(&session->lock, flags);
	if (session->state != ISCSI_SESSION_LOGGED_IN) {
		spin_unlock_irqrestore(&session->lock, flags);
		goto user_scan_exit;
	}
	id = session->target_id;
	spin_unlock_irqrestore(&session->lock, flags);

	if (id != ISCSI_MAX_TARGET) {
		if ((scan_data->channel == SCAN_WILD_CARD ||
		     scan_data->channel == 0) &&
		    (scan_data->id == SCAN_WILD_CARD ||
		     scan_data->id == id))
			scsi_scan_target(&session->dev, 0, id,
					 scan_data->lun, scan_data->rescan);
	}

user_scan_exit:
	mutex_unlock(&ihost->mutex);


}
EXPORT_SYMBOL_GPL(iscsi_alloc_session);

int iscsi_add_session(struct iscsi_cls_session *session, unsigned int target_id)
{
	unsigned long flags;
	int id = 0;
	int err;

	session->sid = atomic_add_return(1, &iscsi_session_nr);

	if (target_id == ISCSI_MAX_TARGET) {
		id = ida_simple_get(&iscsi_sess_ida, 0, 0, GFP_KERNEL);

		if (id < 0) {
			iscsi_cls_session_printk(KERN_ERR, session,
					"Failure in Target ID Allocation\n");
			return id;
		}
		session->target_id = (unsigned int)id;
		session->ida_used = true;
	} else
		session->target_id = target_id;

	dev_set_name(&session->dev, "session%u", session->sid);
	err = device_add(&session->dev);
	if (err) {
		iscsi_cls_session_printk(KERN_ERR, session,
					 "could not register session's dev\n");
		goto release_ida;
	}
	err = transport_register_device(&session->dev);
	if (err) {
		iscsi_cls_session_printk(KERN_ERR, session,
					 "could not register transport's dev\n");
		goto release_dev;
	}

	spin_lock_irqsave(&sesslock, flags);
	list_add(&session->sess_list, &sesslist);
	spin_unlock_irqrestore(&sesslock, flags);

	iscsi_session_event(session, ISCSI_KEVENT_CREATE_SESSION);
	ISCSI_DBG_TRANS_SESSION(session, "Completed session adding\n");
	return 0;

release_dev:
	device_del(&session->dev);
release_ida:
	if (session->ida_used)
		ida_simple_remove(&iscsi_sess_ida, session->target_id);


static ISCSI_CLASS_ATTR(priv_sess, state, S_IRUGO, show_priv_session_state,
			NULL);
static ssize_t
show_priv_session_creator(struct device *dev, struct device_attribute *attr,
			char *buf)
{


	[ISCSI_CONN_FAILED] = "failed"
};

static ssize_t show_conn_state(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent);
	const char *state = "unknown";

	if (conn->state >= 0 &&
	    conn->state < ARRAY_SIZE(connection_state_names))
		state = connection_state_names[conn->state];


 * Deletes the flashnode session entry and all children flashnode connection
 * entries from sysfs
 */
void iscsi_destroy_flashnode_sess(struct iscsi_bus_flash_session *fnode_sess)
{
	int err;

	err = device_for_each_child(&fnode_sess->dev, NULL,
				    iscsi_iter_destroy_flashnode_conn_fn);
	if (err)
		pr_err("Could not delete all connections for %s. Error %d.\n",
		       fnode_sess->dev.kobj.name, err);


}
EXPORT_SYMBOL_GPL(iscsi_destroy_flashnode_sess);

static int iscsi_iter_destroy_flashnode_fn(struct device *dev, void *data)
{
	if (!iscsi_flashnode_bus_match(dev, NULL))
		return 0;


	return 0;
}

static int flashnode_match_index(struct device *dev, void *data)
{
	struct iscsi_bus_flash_session *fnode_sess = NULL;
	int ret = 0;

	if (!iscsi_flashnode_bus_match(dev, NULL))
		goto exit_match_index;

	fnode_sess = iscsi_dev_to_flash_session(dev);
	ret = (fnode_sess->target_id == *((int *)data)) ? 1 : 0;


}
EXPORT_SYMBOL_GPL(iscsi_conn_error_event);

void iscsi_conn_login_event(struct iscsi_cls_conn *conn,
			    enum iscsi_conn_state state)
{
	struct nlmsghdr *nlh;
	struct sk_buff  *skb;
	struct iscsi_uevent *ev;
	struct iscsi_internal *priv;
	int len = nlmsg_total_size(sizeof(*ev));

	priv = iscsi_if_transport_lookup(conn->transport);
	if (!priv)
		return;

	skb = alloc_skb(len, GFP_ATOMIC);
	if (!skb) {
		iscsi_cls_conn_printk(KERN_ERR, conn, "gracefully ignored "
				      "conn login (%d)\n", state);
		return;
	}

	nlh = __nlmsg_put(skb, 0, 0, 0, (len - sizeof(*nlh)), 0);
	ev = nlmsg_data(nlh);
	ev->transport_handle = iscsi_handle(conn->transport);
	ev->type = ISCSI_KEVENT_CONN_LOGIN_STATE;
	ev->r.conn_login.state = state;
	ev->r.conn_login.cid = conn->cid;
	ev->r.conn_login.sid = iscsi_conn_get_sid(conn);
	iscsi_multicast_skb(skb, ISCSI_NL_GRP_ISCSID, GFP_ATOMIC);


	return err;
}

static int iscsi_login_flashnode(struct iscsi_transport *transport,
				 struct iscsi_uevent *ev)
{
	struct Scsi_Host *shost;
	struct iscsi_bus_flash_session *fnode_sess;
	struct iscsi_bus_flash_conn *fnode_conn;
	struct device *dev;
	uint32_t idx;
	int err = 0;

	if (!transport->login_flashnode) {
		err = -ENOSYS;
		goto exit_login_fnode;
	}

	shost = scsi_host_lookup(ev->u.login_flashnode.host_no);
	if (!shost) {
		pr_err("%s could not find host no %u\n",
		       __func__, ev->u.login_flashnode.host_no);
		err = -ENODEV;
		goto put_host;
	}

	idx = ev->u.login_flashnode.flashnode_idx;
	fnode_sess = iscsi_get_flashnode_by_index(shost, idx);
	if (!fnode_sess) {
		pr_err("%s could not find flashnode %u for host no %u\n",
		       __func__, idx, ev->u.login_flashnode.host_no);
		err = -ENODEV;
		goto put_host;
	}

	dev = iscsi_find_flashnode_conn(fnode_sess);
	if (!dev) {
		err = -ENODEV;
		goto put_sess;
	}

	fnode_conn = iscsi_dev_to_flash_conn(dev);
	err = transport->login_flashnode(fnode_sess, fnode_conn);
	put_device(dev);

put_sess:
	put_device(&fnode_sess->dev);

put_host:
	scsi_host_put(shost);


};

static void iscsi_flashnode_sess_release(struct device *dev)
{
	struct iscsi_bus_flash_session *fnode_sess =
						iscsi_dev_to_flash_session(dev);

	kfree(fnode_sess->targetname);
	kfree(fnode_sess->targetalias);
	kfree(fnode_sess->portal_type);


}
EXPORT_SYMBOL_GPL(iscsi_register_transport);

int iscsi_unregister_transport(struct iscsi_transport *tt)
{
	struct iscsi_internal *priv;
	unsigned long flags;

	BUG_ON(!tt);

	mutex_lock(&rx_queue_mutex);

	priv = iscsi_if_transport_lookup(tt);
	BUG_ON (!priv);

	spin_lock_irqsave(&iscsi_transport_lock, flags);
	list_del(&priv->list);
	spin_unlock_irqrestore(&iscsi_transport_lock, flags);

	transport_container_unregister(&priv->conn_cont);
	transport_container_unregister(&priv->session_cont);
	transport_container_unregister(&priv->t.host_attrs);

	sysfs_remove_group(&priv->dev.kobj, &iscsi_transport_group);
	device_unregister(&priv->dev);
	mutex_unlock(&rx_queue_mutex);


	NULL,
};

static umode_t iscsi_session_attr_is_visible(struct kobject *kobj,
					    struct attribute *attr, int i)
{
	struct device *cdev = container_of(kobj, struct device, kobj);
	struct iscsi_cls_session *session = transport_class_to_session(cdev);
	struct iscsi_transport *t = session->transport;
	int param;

	if (attr == &dev_attr_sess_initial_r2t.attr)
		param = ISCSI_PARAM_INITIAL_R2T_EN;
	else if (attr == &dev_attr_sess_max_outstanding_r2t.attr)
		param = ISCSI_PARAM_MAX_R2T;
	else if (attr == &dev_attr_sess_immediate_data.attr)
		param = ISCSI_PARAM_IMM_DATA_EN;
	else if (attr == &dev_attr_sess_first_burst_len.attr)
		param = ISCSI_PARAM_FIRST_BURST;
	else if (attr == &dev_attr_sess_max_burst_len.attr)
		param = ISCSI_PARAM_MAX_BURST;
	else if (attr == &dev_attr_sess_data_pdu_in_order.attr)
		param = ISCSI_PARAM_PDU_INORDER_EN;
	else if (attr == &dev_attr_sess_data_seq_in_order.attr)
		param = ISCSI_PARAM_DATASEQ_INORDER_EN;
	else if (attr == &dev_attr_sess_erl.attr)
		param = ISCSI_PARAM_ERL;
	else if (attr == &dev_attr_sess_targetname.attr)
		param = ISCSI_PARAM_TARGET_NAME;
	else if (attr == &dev_attr_sess_tpgt.attr)
		param = ISCSI_PARAM_TPGT;
	else if (attr == &dev_attr_sess_chap_in_idx.attr)
		param = ISCSI_PARAM_CHAP_IN_IDX;
	else if (attr == &dev_attr_sess_chap_out_idx.attr)
		param = ISCSI_PARAM_CHAP_OUT_IDX;
	else if (attr == &dev_attr_sess_password.attr)
		param = ISCSI_PARAM_USERNAME;
	else if (attr == &dev_attr_sess_password_in.attr)
		param = ISCSI_PARAM_USERNAME_IN;
	else if (attr == &dev_attr_sess_username.attr)
		param = ISCSI_PARAM_PASSWORD;
	else if (attr == &dev_attr_sess_username_in.attr)
		param = ISCSI_PARAM_PASSWORD_IN;
	else if (attr == &dev_attr_sess_fast_abort.attr)
		param = ISCSI_PARAM_FAST_ABORT;
	else if (attr == &dev_attr_sess_abort_tmo.attr)
		param = ISCSI_PARAM_ABORT_TMO;
	else if (attr == &dev_attr_sess_lu_reset_tmo.attr)
		param = ISCSI_PARAM_LU_RESET_TMO;
	else if (attr == &dev_attr_sess_tgt_reset_tmo.attr)
		param = ISCSI_PARAM_TGT_RESET_TMO;
	else if (attr == &dev_attr_sess_ifacename.attr)
		param = ISCSI_PARAM_IFACE_NAME;
	else if (attr == &dev_attr_sess_initiatorname.attr)
		param = ISCSI_PARAM_INITIATOR_NAME;
	else if (attr == &dev_attr_sess_targetalias.attr)
		param = ISCSI_PARAM_TARGET_ALIAS;
	else if (attr == &dev_attr_sess_boot_root.attr)
		param = ISCSI_PARAM_BOOT_ROOT;
	else if (attr == &dev_attr_sess_boot_nic.attr)
		param = ISCSI_PARAM_BOOT_NIC;
	else if (attr == &dev_attr_sess_boot_target.attr)
		param = ISCSI_PARAM_BOOT_TARGET;
	else if (attr == &dev_attr_sess_auto_snd_tgt_disable.attr)
		param = ISCSI_PARAM_AUTO_SND_TGT_DISABLE;
	else if (attr == &dev_attr_sess_discovery_session.attr)
		param = ISCSI_PARAM_DISCOVERY_SESS;
	else if (attr == &dev_attr_sess_portal_type.attr)
		param = ISCSI_PARAM_PORTAL_TYPE;
	else if (attr == &dev_attr_sess_chap_auth.attr)
		param = ISCSI_PARAM_CHAP_AUTH_EN;
	else if (attr == &dev_attr_sess_discovery_logout.attr)
		param = ISCSI_PARAM_DISCOVERY_LOGOUT_EN;
	else if (attr == &dev_attr_sess_bidi_chap.attr)
		param = ISCSI_PARAM_BIDI_CHAP_EN;
	else if (attr == &dev_attr_sess_discovery_auth_optional.attr)
		param = ISCSI_PARAM_DISCOVERY_AUTH_OPTIONAL;
	else if (attr == &dev_attr_sess_def_time2wait.attr)
		param = ISCSI_PARAM_DEF_TIME2WAIT;
	else if (attr == &dev_attr_sess_def_time2retain.attr)
		param = ISCSI_PARAM_DEF_TIME2RETAIN;
	else if (attr == &dev_attr_sess_isid.attr)
		param = ISCSI_PARAM_ISID;
	else if (attr == &dev_attr_sess_tsid.attr)
		param = ISCSI_PARAM_TSID;
	else if (attr == &dev_attr_sess_def_taskmgmt_tmo.attr)
		param = ISCSI_PARAM_DEF_TASKMGMT_TMO;
	else if (attr == &dev_attr_sess_discovery_parent_idx.attr)
		param = ISCSI_PARAM_DISCOVERY_PARENT_IDX;
	else if (attr == &dev_attr_sess_discovery_parent_type.attr)
		param = ISCSI_PARAM_DISCOVERY_PARENT_TYPE;
	else if (attr == &dev_attr_priv_sess_recovery_tmo.attr)
		return S_IRUGO | S_IWUSR;
	else if (attr == &dev_attr_priv_sess_state.attr)
		return S_IRUGO;
	else if (attr == &dev_attr_priv_sess_creator.attr)
		return S_IRUGO;
	else if (attr == &dev_attr_priv_sess_target_id.attr)
		return S_IRUGO;
	else {
		WARN_ONCE(1, "Invalid session attr");
		return 0;
	}


}
EXPORT_SYMBOL_GPL(iscsi_block_scsi_eh);

static void session_recovery_timedout(struct work_struct *work)
{
	struct iscsi_cls_session *session =
		container_of(work, struct iscsi_cls_session,
			     recovery_work.work);
	unsigned long flags;

	iscsi_cls_session_printk(KERN_INFO, session,
				 "session recovery timed out after %d secs\n",
				 session->recovery_tmo);

	spin_lock_irqsave(&session->lock, flags);
	switch (session->state) {
	case ISCSI_SESSION_FAILED:
		session->state = ISCSI_SESSION_FREE;
		break;
	case ISCSI_SESSION_LOGGED_IN:
	case ISCSI_SESSION_FREE:
		/* we raced with the unblock's flush */
		spin_unlock_irqrestore(&session->lock, flags);
		return;
	}
	spin_unlock_irqrestore(&session->lock, flags);

	if (session->transport->session_recovery_timedout)
		session->transport->session_recovery_timedout(session);

	ISCSI_DBG_TRANS_SESSION(session, "Unblocking SCSI target\n");


 *  %NULL on failure
 */
struct device *
iscsi_find_flashnode_sess(struct Scsi_Host *shost, void *data,
			  int (*fn)(struct device *dev, void *data))


}

static int
iscsi_if_create_conn(struct iscsi_transport *transport, struct iscsi_uevent *ev)
{
	struct iscsi_cls_conn *conn;
	struct iscsi_cls_session *session;

	session = iscsi_session_lookup(ev->u.c_conn.sid);
	if (!session) {
		printk(KERN_ERR "iscsi: invalid session %d.\n",
		       ev->u.c_conn.sid);
		return -EINVAL;
	}

	conn = transport->create_conn(session, ev->u.c_conn.cid);
	if (!conn) {
		iscsi_cls_session_printk(KERN_ERR, session,
					 "couldn't create a new connection.");
		return -ENOMEM;
	}

	ev->r.c_conn_ret.sid = session->sid;
	ev->r.c_conn_ret.cid = conn->cid;


 * This function can be used by drives like qla4xxx to report to the scsi
 * layer when the scans it kicked off at module load time are done.
 */
int iscsi_scan_finished(struct Scsi_Host *shost, unsigned long time)
{
	struct iscsi_cls_host *ihost = shost->shost_data;
	/*
	 * qla4xxx will have kicked off some session unblocks before calling
	 * scsi_scan_host, so just wait for them to complete.


static int
iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group)
{
	int err = 0;
	u32 portid;
	u32 pdu_len;
	struct iscsi_uevent *ev = nlmsg_data(nlh);
	struct iscsi_transport *transport = NULL;
	struct iscsi_internal *priv;
	struct iscsi_cls_session *session;
	struct iscsi_cls_conn *conn;
	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);
		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(iscsi_destroy_workq, &session->destroy_work);
		}
		break;
	case ISCSI_UEVENT_UNBIND_SESSION:
		session = iscsi_session_lookup(ev->u.d_session.sid);
		if (session)
			scsi_queue_work(iscsi_session_to_shost(session),
					&session->unbind_work);
		else
			err = -EINVAL;
		break;
	case ISCSI_UEVENT_CREATE_CONN:
		err = iscsi_if_create_conn(transport, ev);
		break;
	case ISCSI_UEVENT_DESTROY_CONN:
		err = iscsi_if_destroy_conn(transport, ev);
		break;
	case ISCSI_UEVENT_BIND_CONN:
		session = iscsi_session_lookup(ev->u.b_conn.sid);
		conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid);

		if (conn && conn->ep)
			iscsi_if_ep_disconnect(transport, conn->ep->id);

		if (!session || !conn) {
			err = -EINVAL;
			break;
		}

		mutex_lock(&conn_mutex);
		ev->r.retcode =	transport->bind_conn(session, conn,
						ev->u.b_conn.transport_eph,
						ev->u.b_conn.is_leading);
		mutex_unlock(&conn_mutex);

		if (ev->r.retcode || !transport->ep_connect)
			break;

		ep = iscsi_lookup_endpoint(ev->u.b_conn.transport_eph);
		if (ep) {
			ep->conn = conn;

			mutex_lock(&conn->ep_mutex);
			conn->ep = ep;
			mutex_unlock(&conn->ep_mutex);
		} else
			iscsi_cls_conn_printk(KERN_ERR, conn,
					      "Could not set ep conn "
					      "binding\n");
		break;
	case ISCSI_UEVENT_SET_PARAM:
		err = iscsi_set_param(transport, ev);
		break;
	case ISCSI_UEVENT_START_CONN:
		conn = iscsi_conn_lookup(ev->u.start_conn.sid, ev->u.start_conn.cid);
		if (conn) {
			mutex_lock(&conn_mutex);
			ev->r.retcode = transport->start_conn(conn);
			if (!ev->r.retcode)
				conn->state = ISCSI_CONN_UP;
			mutex_unlock(&conn_mutex);
		}
		else
			err = -EINVAL;
		break;
	case ISCSI_UEVENT_STOP_CONN:
		conn = iscsi_conn_lookup(ev->u.stop_conn.sid, ev->u.stop_conn.cid);
		if (conn)
			iscsi_if_stop_conn(conn, ev->u.stop_conn.flag);
		else
			err = -EINVAL;
		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;
		}

		conn = iscsi_conn_lookup(ev->u.send_pdu.sid, ev->u.send_pdu.cid);
		if (conn) {
			mutex_lock(&conn_mutex);
			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);
			mutex_unlock(&conn_mutex);
		}
		else
			err = -EINVAL;
		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;
	}


};

static umode_t iscsi_flashnode_sess_attr_is_visible(struct kobject *kobj,
						    struct attribute *attr,
						    int i)
{
	struct device *dev = container_of(kobj, struct device, kobj);
	struct iscsi_bus_flash_session *fnode_sess =
						iscsi_dev_to_flash_session(dev);
	struct iscsi_transport *t = fnode_sess->transport;
	int param;

	if (attr == &dev_attr_fnode_auto_snd_tgt_disable.attr) {
		param = ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE;
	} else if (attr == &dev_attr_fnode_discovery_session.attr) {
		param = ISCSI_FLASHNODE_DISCOVERY_SESS;
	} else if (attr == &dev_attr_fnode_portal_type.attr) {
		param = ISCSI_FLASHNODE_PORTAL_TYPE;
	} else if (attr == &dev_attr_fnode_entry_enable.attr) {
		param = ISCSI_FLASHNODE_ENTRY_EN;
	} else if (attr == &dev_attr_fnode_immediate_data.attr) {
		param = ISCSI_FLASHNODE_IMM_DATA_EN;
	} else if (attr == &dev_attr_fnode_initial_r2t.attr) {
		param = ISCSI_FLASHNODE_INITIAL_R2T_EN;
	} else if (attr == &dev_attr_fnode_data_seq_in_order.attr) {
		param = ISCSI_FLASHNODE_DATASEQ_INORDER;
	} else if (attr == &dev_attr_fnode_data_pdu_in_order.attr) {
		param = ISCSI_FLASHNODE_PDU_INORDER;
	} else if (attr == &dev_attr_fnode_chap_auth.attr) {
		param = ISCSI_FLASHNODE_CHAP_AUTH_EN;
	} else if (attr == &dev_attr_fnode_discovery_logout.attr) {
		param = ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN;
	} else if (attr == &dev_attr_fnode_bidi_chap.attr) {
		param = ISCSI_FLASHNODE_BIDI_CHAP_EN;
	} else if (attr == &dev_attr_fnode_discovery_auth_optional.attr) {
		param = ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL;
	} else if (attr == &dev_attr_fnode_erl.attr) {
		param = ISCSI_FLASHNODE_ERL;
	} else if (attr == &dev_attr_fnode_first_burst_len.attr) {
		param = ISCSI_FLASHNODE_FIRST_BURST;
	} else if (attr == &dev_attr_fnode_def_time2wait.attr) {
		param = ISCSI_FLASHNODE_DEF_TIME2WAIT;
	} else if (attr == &dev_attr_fnode_def_time2retain.attr) {
		param = ISCSI_FLASHNODE_DEF_TIME2RETAIN;
	} else if (attr == &dev_attr_fnode_max_outstanding_r2t.attr) {
		param = ISCSI_FLASHNODE_MAX_R2T;
	} else if (attr == &dev_attr_fnode_isid.attr) {
		param = ISCSI_FLASHNODE_ISID;
	} else if (attr == &dev_attr_fnode_tsid.attr) {
		param = ISCSI_FLASHNODE_TSID;
	} else if (attr == &dev_attr_fnode_max_burst_len.attr) {
		param = ISCSI_FLASHNODE_MAX_BURST;
	} else if (attr == &dev_attr_fnode_def_taskmgmt_tmo.attr) {
		param = ISCSI_FLASHNODE_DEF_TASKMGMT_TMO;
	} else if (attr == &dev_attr_fnode_targetalias.attr) {
		param = ISCSI_FLASHNODE_ALIAS;
	} else if (attr == &dev_attr_fnode_targetname.attr) {
		param = ISCSI_FLASHNODE_NAME;
	} else if (attr == &dev_attr_fnode_tpgt.attr) {
		param = ISCSI_FLASHNODE_TPGT;
	} else if (attr == &dev_attr_fnode_discovery_parent_idx.attr) {
		param = ISCSI_FLASHNODE_DISCOVERY_PARENT_IDX;
	} else if (attr == &dev_attr_fnode_discovery_parent_type.attr) {
		param = ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE;
	} else if (attr == &dev_attr_fnode_chap_in_idx.attr) {
		param = ISCSI_FLASHNODE_CHAP_IN_IDX;
	} else if (attr == &dev_attr_fnode_chap_out_idx.attr) {
		param = ISCSI_FLASHNODE_CHAP_OUT_IDX;
	} else if (attr == &dev_attr_fnode_username.attr) {
		param = ISCSI_FLASHNODE_USERNAME;
	} else if (attr == &dev_attr_fnode_username_in.attr) {
		param = ISCSI_FLASHNODE_USERNAME_IN;
	} else if (attr == &dev_attr_fnode_password.attr) {
		param = ISCSI_FLASHNODE_PASSWORD;
	} else if (attr == &dev_attr_fnode_password_in.attr) {
		param = ISCSI_FLASHNODE_PASSWORD_IN;
	} else if (attr == &dev_attr_fnode_is_boot_target.attr) {
		param = ISCSI_FLASHNODE_IS_BOOT_TGT;
	} else {
		WARN_ONCE(1, "Invalid flashnode session attr");
		return 0;
	}


}

static int
iscsi_get_chap(struct iscsi_transport *transport, struct nlmsghdr *nlh)
{
	struct iscsi_uevent *ev = nlmsg_data(nlh);
	struct Scsi_Host *shost = NULL;
	struct iscsi_chap_rec *chap_rec;
	struct iscsi_internal *priv;
	struct sk_buff *skbchap;
	struct nlmsghdr *nlhchap;
	struct iscsi_uevent *evchap;
	uint32_t chap_buf_size;
	int len, err = 0;
	char *buf;

	if (!transport->get_chap)
		return -EINVAL;

	priv = iscsi_if_transport_lookup(transport);
	if (!priv)
		return -EINVAL;

	chap_buf_size = (ev->u.get_chap.num_entries * sizeof(*chap_rec));
	len = nlmsg_total_size(sizeof(*ev) + chap_buf_size);

	shost = scsi_host_lookup(ev->u.get_chap.host_no);
	if (!shost) {
		printk(KERN_ERR "%s: failed. Could not find host no %u\n",
		       __func__, ev->u.get_chap.host_no);
		return -ENODEV;
	}

	do {
		int actual_size;

		skbchap = alloc_skb(len, GFP_KERNEL);
		if (!skbchap) {
			printk(KERN_ERR "can not deliver chap: OOM\n");
			err = -ENOMEM;
			goto exit_get_chap;
		}

		nlhchap = __nlmsg_put(skbchap, 0, 0, 0,
				      (len - sizeof(*nlhchap)), 0);
		evchap = nlmsg_data(nlhchap);
		memset(evchap, 0, sizeof(*evchap));
		evchap->transport_handle = iscsi_handle(transport);
		evchap->type = nlh->nlmsg_type;
		evchap->u.get_chap.host_no = ev->u.get_chap.host_no;
		evchap->u.get_chap.chap_tbl_idx = ev->u.get_chap.chap_tbl_idx;
		evchap->u.get_chap.num_entries = ev->u.get_chap.num_entries;
		buf = (char *)evchap + sizeof(*evchap);
		memset(buf, 0, chap_buf_size);

		err = transport->get_chap(shost, ev->u.get_chap.chap_tbl_idx,
				    &evchap->u.get_chap.num_entries, buf);

		actual_size = nlmsg_total_size(sizeof(*ev) + chap_buf_size);
		skb_trim(skbchap, NLMSG_ALIGN(actual_size));
		nlhchap->nlmsg_len = actual_size;

		err = iscsi_multicast_skb(skbchap, ISCSI_NL_GRP_ISCSID,
					  GFP_KERNEL);
	} while (err < 0 && err != -ECONNREFUSED);

exit_get_chap:


	return err;
}

static int iscsi_logout_flashnode_sid(struct iscsi_transport *transport,
				      struct iscsi_uevent *ev)
{
	struct Scsi_Host *shost;
	struct iscsi_cls_session *session;
	int err = 0;

	if (!transport->logout_flashnode_sid) {
		err = -ENOSYS;
		goto exit_logout_sid;
	}

	shost = scsi_host_lookup(ev->u.logout_flashnode_sid.host_no);
	if (!shost) {
		pr_err("%s could not find host no %u\n",
		       __func__, ev->u.logout_flashnode.host_no);
		err = -ENODEV;
		goto put_host;
	}

	session = iscsi_session_lookup(ev->u.logout_flashnode_sid.sid);
	if (!session) {
		pr_err("%s could not find session id %u\n",
		       __func__, ev->u.logout_flashnode_sid.sid);
		err = -EINVAL;
		goto put_host;
	}

	err = transport->logout_flashnode_sid(session);

put_host:
	scsi_host_put(shost);


}

static int
iscsi_set_param(struct iscsi_transport *transport, struct iscsi_uevent *ev)
{
	char *data = (char*)ev + sizeof(*ev);
	struct iscsi_cls_conn *conn;
	struct iscsi_cls_session *session;
	int err = 0, value = 0;

	if (ev->u.set_param.len > PAGE_SIZE)
		return -EINVAL;

	session = iscsi_session_lookup(ev->u.set_param.sid);
	conn = iscsi_conn_lookup(ev->u.set_param.sid, ev->u.set_param.cid);
	if (!conn || !session)
		return -EINVAL;

	switch (ev->u.set_param.param) {
	case ISCSI_PARAM_SESS_RECOVERY_TMO:
		sscanf(data, "%d", &value);
		if (!session->recovery_tmo_sysfs_override)
			session->recovery_tmo = value;
		break;
	default:
		err = transport->set_param(conn, ev->u.set_param.param,
					   data, ev->u.set_param.len);
	}


	return name;
}

int iscsi_session_chkready(struct iscsi_cls_session *session)
{
	int err;

	switch (session->state) {
	case ISCSI_SESSION_LOGGED_IN:
		err = 0;
		break;
	case ISCSI_SESSION_FAILED:
		err = DID_IMM_RETRY << 16;
		break;
	case ISCSI_SESSION_FREE:
		err = DID_TRANSPORT_FAILFAST << 16;
		break;
	default:
		err = DID_NO_CONNECT << 16;
		break;


}
EXPORT_SYMBOL_GPL(iscsi_create_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;

	ISCSI_DBG_TRANS_CONN(conn, "Releasing conn\n");


 *  %NULL on failure
 */
struct iscsi_bus_flash_conn *
iscsi_create_flashnode_conn(struct Scsi_Host *shost,
			    struct iscsi_bus_flash_session *fnode_sess,
			    struct iscsi_transport *transport,
			    int dd_size)
{
	struct iscsi_bus_flash_conn *fnode_conn;
	int err;

	fnode_conn = kzalloc(sizeof(*fnode_conn) + dd_size, GFP_KERNEL);
	if (!fnode_conn)
		return NULL;

	fnode_conn->transport = transport;
	fnode_conn->dev.type = &iscsi_flashnode_conn_dev_type;
	fnode_conn->dev.bus = &iscsi_flashnode_bus;
	fnode_conn->dev.parent = &fnode_sess->dev;
	dev_set_name(&fnode_conn->dev, "flashnode_conn-%u:%u:0",
		     shost->host_no, fnode_sess->target_id);

	err = device_register(&fnode_conn->dev);
	if (err)
		goto free_fnode_conn;

	if (dd_size)
		fnode_conn->dd_data = &fnode_conn[1];

	return fnode_conn;

free_fnode_conn:


}
EXPORT_SYMBOL_GPL(iscsi_recv_pdu);

int iscsi_offload_mesg(struct Scsi_Host *shost,
		       struct iscsi_transport *transport, uint32_t type,
		       char *data, uint16_t data_size)
{
	struct nlmsghdr	*nlh;
	struct sk_buff *skb;
	struct iscsi_uevent *ev;
	int len = nlmsg_total_size(sizeof(*ev) + data_size);

	skb = alloc_skb(len, GFP_ATOMIC);
	if (!skb) {
		printk(KERN_ERR "can not deliver iscsi offload message:OOM\n");
		return -ENOMEM;
	}

	nlh = __nlmsg_put(skb, 0, 0, 0, (len - sizeof(*nlh)), 0);
	ev = nlmsg_data(nlh);
	memset(ev, 0, sizeof(*ev));
	ev->type = type;
	ev->transport_handle = iscsi_handle(transport);
	switch (type) {
	case ISCSI_KEVENT_PATH_REQ:
		ev->r.req_path.host_no = shost->host_no;
		break;
	case ISCSI_KEVENT_IF_DOWN:
		ev->r.notify_if_down.host_no = shost->host_no;
		break;
	}

	memcpy((char *)ev + sizeof(*ev), data, data_size);


static void iscsi_endpoint_release(struct device *dev)
{
	struct iscsi_endpoint *ep = iscsi_dev_to_endpoint(dev);
	kfree(ep);
}


}

static int
iscsi_if_get_stats(struct iscsi_transport *transport, 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;
	struct iscsi_internal *priv;
	int len = nlmsg_total_size(sizeof(*ev) +
				   sizeof(struct iscsi_stats) +
				   sizeof(struct iscsi_stats_custom) *
				   ISCSI_STATS_CUSTOM_MAX);
	int err = 0;

	priv = iscsi_if_transport_lookup(transport);
	if (!priv)
		return -EINVAL;

	conn = iscsi_conn_lookup(ev->u.get_stats.sid, ev->u.get_stats.cid);
	if (!conn)
		return -EEXIST;

	do {
		int actual_size;

		skbstat = alloc_skb(len, GFP_ATOMIC);
		if (!skbstat) {
			iscsi_cls_conn_printk(KERN_ERR, conn, "can not "
					      "deliver stats: OOM\n");
			return -ENOMEM;
		}

		nlhstat = __nlmsg_put(skbstat, 0, 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;
		evstat->u.get_stats.cid =
			ev->u.get_stats.cid;
		evstat->u.get_stats.sid =
			ev->u.get_stats.sid;
		stats = (struct iscsi_stats *)
			((char*)evstat + sizeof(*evstat));
		memset(stats, 0, sizeof(*stats));

		transport->get_stats(conn, stats);
		actual_size = nlmsg_total_size(sizeof(struct iscsi_uevent) +
					       sizeof(struct iscsi_stats) +
					       sizeof(struct iscsi_stats_custom) *
					       stats->custom_length);
		actual_size -= sizeof(*nlhstat);
		actual_size = nlmsg_msg_size(actual_size);
		skb_trim(skbstat, NLMSG_ALIGN(actual_size));
		nlhstat->nlmsg_len = actual_size;

		err = iscsi_multicast_skb(skbstat, ISCSI_NL_GRP_ISCSID,
					  GFP_ATOMIC);
	} while (err < 0 && err != -ECONNREFUSED);


static struct bus_type iscsi_flashnode_bus;

int iscsi_flashnode_bus_match(struct device *dev,
				     struct device_driver *drv)
{
	if (dev->bus == &iscsi_flashnode_bus)


}

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:


				     iscsi_user_scan_session);
}

static void iscsi_scan_session(struct work_struct *work)
{
	struct iscsi_cls_session *session =
			container_of(work, struct iscsi_cls_session, scan_work);
	struct Scsi_Host *shost = iscsi_session_to_shost(session);
	struct iscsi_cls_host *ihost = shost->shost_data;
	struct iscsi_scan_data scan_data;

	scan_data.channel = 0;
	scan_data.id = SCAN_WILD_CARD;
	scan_data.lun = SCAN_WILD_CARD;
	scan_data.rescan = SCSI_SCAN_RESCAN;


}
EXPORT_SYMBOL_GPL(iscsi_is_session_dev);

static int iscsi_iter_session_fn(struct device *dev, void *data)
{
	void (* fn) (struct iscsi_cls_session *) = data;

	if (!iscsi_is_session_dev(dev))
		return 0;


}

static int
iscsi_multicast_skb(struct sk_buff *skb, uint32_t group, gfp_t gfp)


	return 0;
}

static int iscsi_remove_host(struct transport_container *tc,
			     struct device *dev, struct device *cdev)
{
	struct Scsi_Host *shost = dev_to_shost(dev);
	struct iscsi_cls_host *ihost = shost->shost_data;


 * stop work. But, in that case, it is guaranteed not to race with
 * iscsi_destroy by conn_mutex.
 */
static void iscsi_if_stop_conn(struct iscsi_cls_conn *conn, int flag)
{
	/*
	 * It is important that this path doesn't rely on
	 * rx_queue_mutex, otherwise, a thread doing allocation on a
	 * start_session/start_connection could sleep waiting on a
	 * writeback to a failed iscsi device, that cannot be recovered
	 * because the lock is held.  If we don't hold it here, the
	 * kernel stop_conn_work_fn has a chance to stop the broken
	 * session and resolve the allocation.
	 *
	 * Still, the user invoked .stop_conn() needs to be serialized
	 * with stop_conn_work_fn by a private mutex.  Not pretty, but
	 * it works.
	 */
	mutex_lock(&conn_mutex);
	conn->transport->stop_conn(conn, flag);


}
EXPORT_SYMBOL_GPL(iscsi_post_host_event);

void iscsi_ping_comp_event(uint32_t host_no, struct iscsi_transport *transport,
			   uint32_t status, uint32_t pid, uint32_t data_size,
			   uint8_t *data)
{
	struct nlmsghdr *nlh;
	struct sk_buff *skb;
	struct iscsi_uevent *ev;
	int len = nlmsg_total_size(sizeof(*ev) + data_size);

	skb = alloc_skb(len, GFP_NOIO);
	if (!skb) {
		printk(KERN_ERR "gracefully ignored ping comp: OOM\n");
		return;
	}

	nlh = __nlmsg_put(skb, 0, 0, 0, (len - sizeof(*nlh)), 0);
	ev = nlmsg_data(nlh);
	ev->transport_handle = iscsi_handle(transport);
	ev->type = ISCSI_KEVENT_PING_COMP;
	ev->r.ping_comp.host_no = host_no;
	ev->r.ping_comp.status = status;
	ev->r.ping_comp.pid = pid;
	ev->r.ping_comp.data_size = data_size;
	memcpy((char *)ev + sizeof(*ev), data, data_size);


struct iscsi_iface *
iscsi_create_iface(struct Scsi_Host *shost, struct iscsi_transport *transport,
		   uint32_t iface_type, uint32_t iface_num, int dd_size)
{
	struct iscsi_iface *iface;
	int err;

	iface = kzalloc(sizeof(*iface) + dd_size, GFP_KERNEL);
	if (!iface)
		return NULL;

	iface->transport = transport;
	iface->iface_type = iface_type;
	iface->iface_num = iface_num;
	iface->dev.release = iscsi_iface_release;
	iface->dev.class = &iscsi_iface_class;
	/* parent reference released in iscsi_iface_release */
	iface->dev.parent = get_device(&shost->shost_gendev);
	if (iface_type == ISCSI_IFACE_TYPE_IPV4)
		dev_set_name(&iface->dev, "ipv4-iface-%u-%u", shost->host_no,
			     iface_num);
	else
		dev_set_name(&iface->dev, "ipv6-iface-%u-%u", shost->host_no,
			     iface_num);

	err = device_register(&iface->dev);
	if (err)
		goto free_iface;

	err = sysfs_create_group(&iface->dev.kobj, &iscsi_iface_group);
	if (err)
		goto unreg_iface;

	if (dd_size)
		iface->dd_data = &iface[1];
	return iface;

unreg_iface:
	device_unregister(&iface->dev);
	return NULL;

free_iface:
	put_device(iface->dev.parent);
	kfree(iface);
	return NULL;


 * Malformed skbs with wrong lengths or invalid creds are not processed.
 */
static void
iscsi_if_rx(struct sk_buff *skb)
{
	u32 portid = NETLINK_CB(skb).portid;

	mutex_lock(&rx_queue_mutex);
	while (skb->len >= NLMSG_HDRLEN) {
		int err;
		uint32_t rlen;
		struct nlmsghdr	*nlh;
		struct iscsi_uevent *ev;
		uint32_t group;
		int retries = ISCSI_SEND_MAX_ALLOWED;

		nlh = nlmsg_hdr(skb);
		if (nlh->nlmsg_len < sizeof(*nlh) + sizeof(*ev) ||
		    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, &group);
		if (err) {
			ev->type = ISCSI_KEVENT_IF_ERROR;
			ev->iferror = err;
		}
		do {
			/*
			 * special case for GET_STATS:
			 * on success - sending reply and stats from
			 * inside of if_recv_msg(),
			 * on error - fall through.
			 */
			if (ev->type == ISCSI_UEVENT_GET_STATS && !err)
				break;
			if (ev->type == ISCSI_UEVENT_GET_CHAP && !err)
				break;
			err = iscsi_if_send_reply(portid, nlh->nlmsg_type,
						  ev, sizeof(*ev));
			if (err == -EAGAIN && --retries < 0) {
				printk(KERN_WARNING "Send reply failed, error %d\n", err);
				break;
			}
		} while (err < 0 && err != -ECONNREFUSED && err != -ESRCH);
		skb_pull(skb, rlen);


}
EXPORT_SYMBOL_GPL(iscsi_conn_login_event);

void iscsi_post_host_event(uint32_t host_no, struct iscsi_transport *transport,
			   enum iscsi_host_event_code code, uint32_t data_size,
			   uint8_t *data)
{
	struct nlmsghdr *nlh;
	struct sk_buff *skb;
	struct iscsi_uevent *ev;
	int len = nlmsg_total_size(sizeof(*ev) + data_size);

	skb = alloc_skb(len, GFP_NOIO);
	if (!skb) {
		printk(KERN_ERR "gracefully ignored host event (%d):%d OOM\n",
		       host_no, code);
		return;
	}

	nlh = __nlmsg_put(skb, 0, 0, 0, (len - sizeof(*nlh)), 0);
	ev = nlmsg_data(nlh);
	ev->transport_handle = iscsi_handle(transport);
	ev->type = ISCSI_KEVENT_HOST_EVENT;
	ev->r.host_event.host_no = host_no;
	ev->r.host_event.code = code;
	ev->r.host_event.data_size = data_size;

	if (data_size)
		memcpy((char *)ev + sizeof(*ev), data, data_size);


	return err;
}

static void __exit iscsi_transport_exit(void)
{
	destroy_workqueue(iscsi_destroy_workq);
	destroy_workqueue(iscsi_eh_timer_workq);
	netlink_kernel_release(nls);
	bus_unregister(&iscsi_flashnode_bus);
	transport_class_unregister(&iscsi_connection_class);
	transport_class_unregister(&iscsi_session_class);
	transport_class_unregister(&iscsi_host_class);
	class_unregister(&iscsi_endpoint_class);


	{ISCSI_PORT_SPEED_40GBPS,       "40 Gbps" },
};

char *iscsi_get_port_speed_name(struct Scsi_Host *shost)
{
	int i;
	char *speed = "Unknown!";
	struct iscsi_cls_host *ihost = shost->shost_data;
	uint32_t port_speed = ihost->port_speed;

	for (i = 0; i < ARRAY_SIZE(iscsi_port_speed_names); i++) {
		if (iscsi_port_speed_names[i].value & port_speed) {
			speed = iscsi_port_speed_names[i].name;
			break;
		}


	return 0;
}

static int iscsi_setup_host(struct transport_container *tc, struct device *dev,
			    struct device *cdev)
{
	struct Scsi_Host *shost = dev_to_shost(dev);
	struct iscsi_cls_host *ihost = shost->shost_data;

	memset(ihost, 0, sizeof(*ihost));
	atomic_set(&ihost->nr_scans, 0);
	mutex_init(&ihost->mutex);

	iscsi_bsg_host_add(shost, ihost);
	/* ignore any bsg add error - we just can't do sgio */


static int dlpar_parse_resource(char **cmd, struct pseries_hp_errorlog *hp_elog)
{
	char *arg;

	arg = strsep(cmd, " ");
	if (!arg)
		return -EINVAL;

	if (sysfs_streq(arg, "memory")) {
		hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_MEM;
	} else if (sysfs_streq(arg, "cpu")) {
		hp_elog->resource = PSERIES_HP_ELOG_RESOURCE_CPU;
	} else {
		pr_err("Invalid resource specified.\n");
		return -EINVAL;
	}

	return 0;
}

powerpc/pseries/dlpar: Fix a missing check in dlpar_parse_cc_property()

In dlpar_parse_cc_property(), 'prop->name' is allocated by kstrdup().
kstrdup() may return NULL, so it should be checked and handle error.
And prop should be freed if 'prop->name' is NULL.

Signed-off-by: Gen Zhang <blackgod016574@gmail.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>


static void pseries_hp_work_fn(struct work_struct *work)
{
	struct pseries_hp_work *hp_work =
			container_of(work, struct pseries_hp_work, work);

	handle_dlpar_errorlog(hp_work->errlog);

	kfree(hp_work->errlog);
	kfree((void *)work);
}


static void dlpar_free_one_cc_node(struct device_node *dn)
{
	struct property *prop;

	while (dn->properties) {
		prop = dn->properties;
		dn->properties = prop->next;
		dlpar_free_cc_property(prop);
	}

	kfree(dn->full_name);
	kfree(dn);
}


void dlpar_free_cc_property(struct property *prop)
{
	kfree(prop->name);
	kfree(prop->value);
	kfree(prop);
}


static struct property *dlpar_parse_cc_property(struct cc_workarea *ccwa)
{
	struct property *prop;
	char *name;
	char *value;

	prop = kzalloc(sizeof(*prop), GFP_KERNEL);
	if (!prop)
		return NULL;

	name = (char *)ccwa + be32_to_cpu(ccwa->name_offset);
	prop->name = kstrdup(name, GFP_KERNEL);
	if (!prop->name) {
		dlpar_free_cc_property(prop);
		return NULL;
	}

	prop->length = be32_to_cpu(ccwa->prop_length);
	value = (char *)ccwa + be32_to_cpu(ccwa->prop_offset);
	prop->value = kmemdup(value, prop->length, GFP_KERNEL);
	if (!prop->value) {
		dlpar_free_cc_property(prop);
		return NULL;
	}

	return prop;
}


int dlpar_detach_node(struct device_node *dn)
{
	struct device_node *child;
	int rc;

	child = of_get_next_child(dn, NULL);
	while (child) {
		dlpar_detach_node(child);
		child = of_get_next_child(dn, child);
	}

	rc = of_detach_node(dn);
	if (rc)
		return rc;

	of_node_put(dn);

	return 0;
}


int dlpar_attach_node(struct device_node *dn, struct device_node *parent)
{
	int rc;

	dn->parent = parent;

	rc = of_attach_node(dn);
	if (rc) {
		printk(KERN_ERR "Failed to add device node %pOF\n", dn);
		return rc;
	}

	return 0;
}


int dlpar_acquire_drc(u32 drc_index)
{
	int dr_status, rc;

	rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
		       DR_ENTITY_SENSE, drc_index);
	if (rc || dr_status != DR_ENTITY_UNUSABLE)
		return -1;

	rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_USABLE);
	if (rc)
		return rc;

	rc = rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
	if (rc) {
		rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
		return rc;
	}

	return 0;
}


void dlpar_free_cc_nodes(struct device_node *dn)
{
	if (dn->child)
		dlpar_free_cc_nodes(dn->child);

	if (dn->sibling)
		dlpar_free_cc_nodes(dn->sibling);

	dlpar_free_one_cc_node(dn);
}


static struct device_node *dlpar_parse_cc_node(struct cc_workarea *ccwa)
{
	struct device_node *dn;
	const char *name;

	dn = kzalloc(sizeof(*dn), GFP_KERNEL);
	if (!dn)
		return NULL;

	name = (const char *)ccwa + be32_to_cpu(ccwa->name_offset);
	dn->full_name = kstrdup(name, GFP_KERNEL);
	if (!dn->full_name) {
		kfree(dn);
		return NULL;
	}

	of_node_set_flag(dn, OF_DYNAMIC);
	of_node_init(dn);

	return dn;
}


static ssize_t dlpar_store(struct class *class, struct class_attribute *attr,
			   const char *buf, size_t count)
{
	struct pseries_hp_errorlog hp_elog;
	char *argbuf;
	char *args;
	int rc;

	args = argbuf = kstrdup(buf, GFP_KERNEL);
	if (!argbuf) {
		pr_info("Could not allocate resources for DLPAR operation\n");
		kfree(argbuf);
		return -ENOMEM;
	}

	/*
	 * Parse out the request from the user, this will be in the form:
	 * <resource> <action> <id_type> <id>
	 */
	rc = dlpar_parse_resource(&args, &hp_elog);
	if (rc)
		goto dlpar_store_out;

	rc = dlpar_parse_action(&args, &hp_elog);
	if (rc)
		goto dlpar_store_out;

	rc = dlpar_parse_id_type(&args, &hp_elog);
	if (rc)
		goto dlpar_store_out;

	rc = handle_dlpar_errorlog(&hp_elog);

dlpar_store_out:
	kfree(argbuf);

	if (rc)
		pr_err("Could not handle DLPAR request \"%s\"\n", buf);

	return rc ? rc : count;
}


static int __init dlpar_sysfs_init(void)
{
	int rc;

	rc = dlpar_workqueue_init();
	if (rc)
		return rc;

	return sysfs_create_file(kernel_kobj, &class_attr_dlpar.attr);
}


struct device_node *dlpar_configure_connector(__be32 drc_index,
					      struct device_node *parent)
{
	struct device_node *dn;
	struct device_node *first_dn = NULL;
	struct device_node *last_dn = NULL;
	struct property *property;
	struct property *last_property = NULL;
	struct cc_workarea *ccwa;
	char *data_buf;
	int cc_token;
	int rc = -1;

	cc_token = rtas_token("ibm,configure-connector");
	if (cc_token == RTAS_UNKNOWN_SERVICE)
		return NULL;

	data_buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL);
	if (!data_buf)
		return NULL;

	ccwa = (struct cc_workarea *)&data_buf[0];
	ccwa->drc_index = drc_index;
	ccwa->zero = 0;

	do {
		/* Since we release the rtas_data_buf lock between configure
		 * connector calls we want to re-populate the rtas_data_buffer
		 * with the contents of the previous call.
		 */
		spin_lock(&rtas_data_buf_lock);

		memcpy(rtas_data_buf, data_buf, RTAS_DATA_BUF_SIZE);
		rc = rtas_call(cc_token, 2, 1, NULL, rtas_data_buf, NULL);
		memcpy(data_buf, rtas_data_buf, RTAS_DATA_BUF_SIZE);

		spin_unlock(&rtas_data_buf_lock);

		switch (rc) {
		case COMPLETE:
			break;

		case NEXT_SIBLING:
			dn = dlpar_parse_cc_node(ccwa);
			if (!dn)
				goto cc_error;

			dn->parent = last_dn->parent;
			last_dn->sibling = dn;
			last_dn = dn;
			break;

		case NEXT_CHILD:
			dn = dlpar_parse_cc_node(ccwa);
			if (!dn)
				goto cc_error;

			if (!first_dn) {
				dn->parent = parent;
				first_dn = dn;
			} else {
				dn->parent = last_dn;
				if (last_dn)
					last_dn->child = dn;
			}

			last_dn = dn;
			break;

		case NEXT_PROPERTY:
			property = dlpar_parse_cc_property(ccwa);
			if (!property)
				goto cc_error;

			if (!last_dn->properties)
				last_dn->properties = property;
			else
				last_property->next = property;

			last_property = property;
			break;

		case PREV_PARENT:
			last_dn = last_dn->parent;
			break;

		case CALL_AGAIN:
			break;

		case MORE_MEMORY:
		case ERR_CFG_USE:
		default:
			printk(KERN_ERR "Unexpected Error (%d) "
			       "returned from configure-connector\n", rc);
			goto cc_error;
		}
	} while (rc);

cc_error:
	kfree(data_buf);

	if (rc) {
		if (first_dn)
			dlpar_free_cc_nodes(first_dn);

		return NULL;
	}

	return first_dn;
}


static int dlpar_parse_id_type(char **cmd, struct pseries_hp_errorlog *hp_elog)
{
	char *arg;
	u32 count, index;

	arg = strsep(cmd, " ");
	if (!arg)
		return -EINVAL;

	if (sysfs_streq(arg, "indexed-count")) {
		hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_IC;
		arg = strsep(cmd, " ");
		if (!arg) {
			pr_err("No DRC count specified.\n");
			return -EINVAL;
		}

		if (kstrtou32(arg, 0, &count)) {
			pr_err("Invalid DRC count specified.\n");
			return -EINVAL;
		}

		arg = strsep(cmd, " ");
		if (!arg) {
			pr_err("No DRC Index specified.\n");
			return -EINVAL;
		}

		if (kstrtou32(arg, 0, &index)) {
			pr_err("Invalid DRC Index specified.\n");
			return -EINVAL;
		}

		hp_elog->_drc_u.ic.count = cpu_to_be32(count);
		hp_elog->_drc_u.ic.index = cpu_to_be32(index);
	} else if (sysfs_streq(arg, "index")) {
		hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_INDEX;
		arg = strsep(cmd, " ");
		if (!arg) {
			pr_err("No DRC Index specified.\n");
			return -EINVAL;
		}

		if (kstrtou32(arg, 0, &index)) {
			pr_err("Invalid DRC Index specified.\n");
			return -EINVAL;
		}

		hp_elog->_drc_u.drc_index = cpu_to_be32(index);
	} else if (sysfs_streq(arg, "count")) {
		hp_elog->id_type = PSERIES_HP_ELOG_ID_DRC_COUNT;
		arg = strsep(cmd, " ");
		if (!arg) {
			pr_err("No DRC count specified.\n");
			return -EINVAL;
		}

		if (kstrtou32(arg, 0, &count)) {
			pr_err("Invalid DRC count specified.\n");
			return -EINVAL;
		}

		hp_elog->_drc_u.drc_count = cpu_to_be32(count);
	} else {
		pr_err("Invalid id_type specified.\n");
		return -EINVAL;
	}

	return 0;
}


static ssize_t dlpar_show(struct class *class, struct class_attribute *attr,
			  char *buf)
{
	return sprintf(buf, "%s\n", "memory,cpu");
}


int handle_dlpar_errorlog(struct pseries_hp_errorlog *hp_elog)
{
	int rc;

	/* pseries error logs are in BE format, convert to cpu type */
	switch (hp_elog->id_type) {
	case PSERIES_HP_ELOG_ID_DRC_COUNT:
		hp_elog->_drc_u.drc_count =
				be32_to_cpu(hp_elog->_drc_u.drc_count);
		break;
	case PSERIES_HP_ELOG_ID_DRC_INDEX:
		hp_elog->_drc_u.drc_index =
				be32_to_cpu(hp_elog->_drc_u.drc_index);
		break;
	case PSERIES_HP_ELOG_ID_DRC_IC:
		hp_elog->_drc_u.ic.count =
				be32_to_cpu(hp_elog->_drc_u.ic.count);
		hp_elog->_drc_u.ic.index =
				be32_to_cpu(hp_elog->_drc_u.ic.index);
	}

	switch (hp_elog->resource) {
	case PSERIES_HP_ELOG_RESOURCE_MEM:
		rc = dlpar_memory(hp_elog);
		break;
	case PSERIES_HP_ELOG_RESOURCE_CPU:
		rc = dlpar_cpu(hp_elog);
		break;
	case PSERIES_HP_ELOG_RESOURCE_PMEM:
		rc = dlpar_hp_pmem(hp_elog);
		break;

	default:
		pr_warn_ratelimited("Invalid resource (%d) specified\n",
				    hp_elog->resource);
		rc = -EINVAL;
	}

	return rc;
}


static int dlpar_parse_action(char **cmd, struct pseries_hp_errorlog *hp_elog)
{
	char *arg;

	arg = strsep(cmd, " ");
	if (!arg)
		return -EINVAL;

	if (sysfs_streq(arg, "add")) {
		hp_elog->action = PSERIES_HP_ELOG_ACTION_ADD;
	} else if (sysfs_streq(arg, "remove")) {
		hp_elog->action = PSERIES_HP_ELOG_ACTION_REMOVE;
	} else {
		pr_err("Invalid action specified.\n");
		return -EINVAL;
	}

	return 0;
}


int dlpar_release_drc(u32 drc_index)
{
	int dr_status, rc;

	rc = rtas_call(rtas_token("get-sensor-state"), 2, 2, &dr_status,
		       DR_ENTITY_SENSE, drc_index);
	if (rc || dr_status != DR_ENTITY_PRESENT)
		return -1;

	rc = rtas_set_indicator(ISOLATION_STATE, drc_index, ISOLATE);
	if (rc)
		return rc;

	rc = rtas_set_indicator(ALLOCATION_STATE, drc_index, ALLOC_UNUSABLE);
	if (rc) {
		rtas_set_indicator(ISOLATION_STATE, drc_index, UNISOLATE);
		return rc;
	}

	return 0;
}


int __init dlpar_workqueue_init(void)
{
	if (pseries_hp_wq)
		return 0;

	pseries_hp_wq = alloc_workqueue("pseries hotplug workqueue",
			WQ_UNBOUND, 1);

	return pseries_hp_wq ? 0 : -ENOMEM;
}


void queue_hotplug_event(struct pseries_hp_errorlog *hp_errlog)
{
	struct pseries_hp_work *work;
	struct pseries_hp_errorlog *hp_errlog_copy;

	hp_errlog_copy = kmalloc(sizeof(struct pseries_hp_errorlog),
				 GFP_KERNEL);
	memcpy(hp_errlog_copy, hp_errlog, sizeof(struct pseries_hp_errorlog));

	work = kmalloc(sizeof(struct pseries_hp_work), GFP_KERNEL);
	if (work) {
		INIT_WORK((struct work_struct *)work, pseries_hp_work_fn);
		work->errlog = hp_errlog_copy;
		queue_work(pseries_hp_wq, (struct work_struct *)work);
	} else {
		kfree(hp_errlog_copy);
	}
}


static void vgacon_cursor(struct vc_data *c, int mode)
{
	if (c->vc_mode != KD_TEXT)
		return;

	vgacon_restore_screen(c);

	switch (mode) {
	case CM_ERASE:
		write_vga(14, (c->vc_pos - vga_vram_base) / 2);
	        if (vga_video_type >= VIDEO_TYPE_VGAC)
			vgacon_set_cursor_size(c->state.x, 31, 30);
		else
			vgacon_set_cursor_size(c->state.x, 31, 31);
		break;

	case CM_MOVE:
	case CM_DRAW:
		write_vga(14, (c->vc_pos - vga_vram_base) / 2);
		switch (CUR_SIZE(c->vc_cursor_type)) {
		case CUR_UNDERLINE:
			vgacon_set_cursor_size(c->state.x,
					       c->vc_font.height -
					       (c->vc_font.height <
						10 ? 2 : 3),
					       c->vc_font.height -
					       (c->vc_font.height <
						10 ? 1 : 2));
			break;
		case CUR_TWO_THIRDS:
			vgacon_set_cursor_size(c->state.x,
					       c->vc_font.height / 3,
					       c->vc_font.height -
					       (c->vc_font.height <
						10 ? 1 : 2));
			break;
		case CUR_LOWER_THIRD:
			vgacon_set_cursor_size(c->state.x,
					       (c->vc_font.height * 2) / 3,
					       c->vc_font.height -
					       (c->vc_font.height <
						10 ? 1 : 2));
			break;
		case CUR_LOWER_HALF:
			vgacon_set_cursor_size(c->state.x,
					       c->vc_font.height / 2,
					       c->vc_font.height -
					       (c->vc_font.height <
						10 ? 1 : 2));
			break;
		case CUR_NONE:
			if (vga_video_type >= VIDEO_TYPE_VGAC)
				vgacon_set_cursor_size(c->state.x, 31, 30);
			else
				vgacon_set_cursor_size(c->state.x, 31, 31);
			break;
		default:
			vgacon_set_cursor_size(c->state.x, 1,
					       c->vc_font.height);
			break;
		}
		break;
	}
}

vgacon: remove software scrollback support

Yunhai Zhang recently fixed a VGA software scrollback bug in commit
ebfdfeeae8c0 ("vgacon: Fix for missing check in scrollback handling"),
but that then made people look more closely at some of this code, and
there were more problems on the vgacon side, but also the fbcon software
scrollback.

We don't really have anybody who maintains this code - probably because
nobody actually _uses_ it any more.  Sure, people still use both VGA and
the framebuffer consoles, but they are no longer the main user
interfaces to the kernel, and haven't been for decades, so these kinds
of extra features end up bitrotting and not really being used.

So rather than try to maintain a likely unused set of code, I'll just
aggressively remove it, and see if anybody even notices.  Maybe there
are people who haven't jumped on the whole GUI badnwagon yet, and think
it's just a fad.  And maybe those people use the scrollback code.

If that turns out to be the case, we can resurrect this again, once
we've found the sucker^Wmaintainer for it who actually uses it.

Reported-by: NopNop Nop <nopitydays@gmail.com>
Tested-by: Willy Tarreau <w@1wt.eu>
Cc: 张云海 <zhangyunhai@nsfocus.com>
Acked-by: Andy Lutomirski <luto@amacapital.net>
Acked-by: Willy Tarreau <w@1wt.eu>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>


static void vga_pal_blank(struct vgastate *state)
{
	int i;

	vga_w(state->vgabase, VGA_PEL_MSK, 0xff);
	for (i = 0; i < 16; i++) {
		vga_w(state->vgabase, VGA_PEL_IW, i);
		vga_w(state->vgabase, VGA_PEL_D, 0);
		vga_w(state->vgabase, VGA_PEL_D, 0);
		vga_w(state->vgabase, VGA_PEL_D, 0);
	}
}


static void vga_vesa_unblank(struct vgastate *state)
{
	/* restore original values of VGA controller registers */
	raw_spin_lock_irq(&vga_lock);
	vga_w(state->vgabase, VGA_MIS_W, vga_state.CrtMiscIO);

	outb_p(0x00, vga_video_port_reg);	/* HorizontalTotal */
	outb_p(vga_state.HorizontalTotal, vga_video_port_val);
	outb_p(0x01, vga_video_port_reg);	/* HorizDisplayEnd */
	outb_p(vga_state.HorizDisplayEnd, vga_video_port_val);
	outb_p(0x04, vga_video_port_reg);	/* StartHorizRetrace */
	outb_p(vga_state.StartHorizRetrace, vga_video_port_val);
	outb_p(0x05, vga_video_port_reg);	/* EndHorizRetrace */
	outb_p(vga_state.EndHorizRetrace, vga_video_port_val);
	outb_p(0x07, vga_video_port_reg);	/* Overflow */
	outb_p(vga_state.Overflow, vga_video_port_val);
	outb_p(0x10, vga_video_port_reg);	/* StartVertRetrace */
	outb_p(vga_state.StartVertRetrace, vga_video_port_val);
	outb_p(0x11, vga_video_port_reg);	/* EndVertRetrace */
	outb_p(vga_state.EndVertRetrace, vga_video_port_val);
	outb_p(0x17, vga_video_port_reg);	/* ModeControl */
	outb_p(vga_state.ModeControl, vga_video_port_val);
	/* ClockingMode */
	vga_wseq(state->vgabase, VGA_SEQ_CLOCK_MODE, vga_state.ClockingMode);

	/* restore index/control registers */
	vga_w(state->vgabase, VGA_SEQ_I, vga_state.SeqCtrlIndex);
	outb_p(vga_state.CrtCtrlIndex, vga_video_port_reg);
	raw_spin_unlock_irq(&vga_lock);
}


static int vgacon_blank(struct vc_data *c, int blank, int mode_switch)
{
	switch (blank) {
	case 0:		/* Unblank */
		if (vga_vesa_blanked) {
			vga_vesa_unblank(&vgastate);
			vga_vesa_blanked = 0;
		}
		if (vga_palette_blanked) {
			vga_set_palette(c, color_table);
			vga_palette_blanked = false;
			return 0;
		}
		vga_is_gfx = false;
		/* Tell console.c that it has to restore the screen itself */
		return 1;
	case 1:		/* Normal blanking */
	case -1:	/* Obsolete */
		if (!mode_switch && vga_video_type == VIDEO_TYPE_VGAC) {
			vga_pal_blank(&vgastate);
			vga_palette_blanked = true;
			return 0;
		}
		vgacon_set_origin(c);
		scr_memsetw((void *) vga_vram_base, BLANK,
			    c->vc_screenbuf_size);
		if (mode_switch)
			vga_is_gfx = true;
		return 1;
	default:		/* VESA blanking */
		if (vga_video_type == VIDEO_TYPE_VGAC) {
			vga_vesa_blank(&vgastate, blank - 1);
			vga_vesa_blanked = blank;
		}
		return 0;
	}
}


static void vgacon_init(struct vc_data *c, int init)
{
	struct uni_pagedir *p;

	/*
	 * We cannot be loaded as a module, therefore init is always 1,
	 * but vgacon_init can be called more than once, and init will
	 * not be 1.
	 */
	c->vc_can_do_color = vga_can_do_color;

	/* set dimensions manually if init != 0 since vc_resize() will fail */
	if (init) {
		c->vc_cols = vga_video_num_columns;
		c->vc_rows = vga_video_num_lines;
	} else
		vc_resize(c, vga_video_num_columns, vga_video_num_lines);

	c->vc_scan_lines = vga_scan_lines;
	c->vc_font.height = vga_video_font_height;
	c->vc_complement_mask = 0x7700;
	if (vga_512_chars)
		c->vc_hi_font_mask = 0x0800;
	p = *c->vc_uni_pagedir_loc;
	if (c->vc_uni_pagedir_loc != &vgacon_uni_pagedir) {
		con_free_unimap(c);
		c->vc_uni_pagedir_loc = &vgacon_uni_pagedir;
		vgacon_refcount++;
	}
	if (!vgacon_uni_pagedir && p)
		con_set_default_unimap(c);

	/* Only set the default if the user didn't deliberately override it */
	if (global_cursor_default == -1)
		global_cursor_default =
			!(screen_info.flags & VIDEO_FLAGS_NOCURSOR);
}


static void vgacon_invert_region(struct vc_data *c, u16 * p, int count)
{
	const bool col = vga_can_do_color;

	while (count--) {
		u16 a = scr_readw(p);
		if (col)
			a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) |
			    (((a) & 0x0700) << 4);
		else
			a ^= ((a & 0x0700) == 0x0100) ? 0x7000 : 0x7700;
		scr_writew(a, p++);
	}
}


static int vgacon_adjust_height(struct vc_data *vc, unsigned fontheight)
{
	unsigned char ovr, vde, fsr;
	int rows, maxscan, i;

	rows = vc->vc_scan_lines / fontheight;	/* Number of video rows we end up with */
	maxscan = rows * fontheight - 1;	/* Scan lines to actually display-1 */

	/* Reprogram the CRTC for the new font size
	   Note: the attempt to read the overflow register will fail
	   on an EGA, but using 0xff for the previous value appears to
	   be OK for EGA text modes in the range 257-512 scan lines, so I
	   guess we don't need to worry about it.

	   The same applies for the spill bits in the font size and cursor
	   registers; they are write-only on EGA, but it appears that they
	   are all don't care bits on EGA, so I guess it doesn't matter. */

	raw_spin_lock_irq(&vga_lock);
	outb_p(0x07, vga_video_port_reg);	/* CRTC overflow register */
	ovr = inb_p(vga_video_port_val);
	outb_p(0x09, vga_video_port_reg);	/* Font size register */
	fsr = inb_p(vga_video_port_val);
	raw_spin_unlock_irq(&vga_lock);

	vde = maxscan & 0xff;	/* Vertical display end reg */
	ovr = (ovr & 0xbd) +	/* Overflow register */
	    ((maxscan & 0x100) >> 7) + ((maxscan & 0x200) >> 3);
	fsr = (fsr & 0xe0) + (fontheight - 1);	/*  Font size register */

	raw_spin_lock_irq(&vga_lock);
	outb_p(0x07, vga_video_port_reg);	/* CRTC overflow register */
	outb_p(ovr, vga_video_port_val);
	outb_p(0x09, vga_video_port_reg);	/* Font size */
	outb_p(fsr, vga_video_port_val);
	outb_p(0x12, vga_video_port_reg);	/* Vertical display limit */
	outb_p(vde, vga_video_port_val);
	raw_spin_unlock_irq(&vga_lock);
	vga_video_font_height = fontheight;

	for (i = 0; i < MAX_NR_CONSOLES; i++) {
		struct vc_data *c = vc_cons[i].d;

		if (c && c->vc_sw == &vga_con) {
			if (con_is_visible(c)) {
			        /* void size to cause regs to be rewritten */
				cursor_size_lastfrom = 0;
				cursor_size_lastto = 0;
				c->vc_sw->con_cursor(c, CM_DRAW);
			}
			c->vc_font.height = fontheight;
			vc_resize(c, 0, rows);	/* Adjust console size */
		}
	}
	return 0;
}


static const char *vgacon_startup(void)
{
	const char *display_desc = NULL;
	u16 saved1, saved2;
	volatile u16 *p;

	if (screen_info.orig_video_isVGA == VIDEO_TYPE_VLFB ||
	    screen_info.orig_video_isVGA == VIDEO_TYPE_EFI) {
	      no_vga:
#ifdef CONFIG_DUMMY_CONSOLE
		conswitchp = &dummy_con;
		return conswitchp->con_startup();
#else
		return NULL;
#endif
	}

	/* boot_params.screen_info reasonably initialized? */
	if ((screen_info.orig_video_lines == 0) ||
	    (screen_info.orig_video_cols  == 0))
		goto no_vga;

	/* VGA16 modes are not handled by VGACON */
	if ((screen_info.orig_video_mode == 0x0D) ||	/* 320x200/4 */
	    (screen_info.orig_video_mode == 0x0E) ||	/* 640x200/4 */
	    (screen_info.orig_video_mode == 0x10) ||	/* 640x350/4 */
	    (screen_info.orig_video_mode == 0x12) ||	/* 640x480/4 */
	    (screen_info.orig_video_mode == 0x6A))	/* 800x600/4 (VESA) */
		goto no_vga;

	vga_video_num_lines = screen_info.orig_video_lines;
	vga_video_num_columns = screen_info.orig_video_cols;
	vgastate.vgabase = NULL;

	if (screen_info.orig_video_mode == 7) {
		/* Monochrome display */
		vga_vram_base = 0xb0000;
		vga_video_port_reg = VGA_CRT_IM;
		vga_video_port_val = VGA_CRT_DM;
		if ((screen_info.orig_video_ega_bx & 0xff) != 0x10) {
			static struct resource ega_console_resource =
			    { .name	= "ega",
			      .flags	= IORESOURCE_IO,
			      .start	= 0x3B0,
			      .end	= 0x3BF };
			vga_video_type = VIDEO_TYPE_EGAM;
			vga_vram_size = 0x8000;
			display_desc = "EGA+";
			request_resource(&ioport_resource,
					 &ega_console_resource);
		} else {
			static struct resource mda1_console_resource =
			    { .name	= "mda",
			      .flags	= IORESOURCE_IO,
			      .start	= 0x3B0,
			      .end	= 0x3BB };
			static struct resource mda2_console_resource =
			    { .name	= "mda",
			      .flags	= IORESOURCE_IO,
			      .start	= 0x3BF,
			      .end	= 0x3BF };
			vga_video_type = VIDEO_TYPE_MDA;
			vga_vram_size = 0x2000;
			display_desc = "*MDA";
			request_resource(&ioport_resource,
					 &mda1_console_resource);
			request_resource(&ioport_resource,
					 &mda2_console_resource);
			vga_video_font_height = 14;
		}
	} else {
		/* If not, it is color. */
		vga_can_do_color = true;
		vga_vram_base = 0xb8000;
		vga_video_port_reg = VGA_CRT_IC;
		vga_video_port_val = VGA_CRT_DC;
		if ((screen_info.orig_video_ega_bx & 0xff) != 0x10) {
			int i;

			vga_vram_size = 0x8000;

			if (!screen_info.orig_video_isVGA) {
				static struct resource ega_console_resource =
				    { .name	= "ega",
				      .flags	= IORESOURCE_IO,
				      .start	= 0x3C0,
				      .end	= 0x3DF };
				vga_video_type = VIDEO_TYPE_EGAC;
				display_desc = "EGA";
				request_resource(&ioport_resource,
						 &ega_console_resource);
			} else {
				static struct resource vga_console_resource =
				    { .name	= "vga+",
				      .flags	= IORESOURCE_IO,
				      .start	= 0x3C0,
				      .end	= 0x3DF };
				vga_video_type = VIDEO_TYPE_VGAC;
				display_desc = "VGA+";
				request_resource(&ioport_resource,
						 &vga_console_resource);

				/*
				 * Normalise the palette registers, to point
				 * the 16 screen colours to the first 16
				 * DAC entries.
				 */

				for (i = 0; i < 16; i++) {
					inb_p(VGA_IS1_RC);
					outb_p(i, VGA_ATT_W);
					outb_p(i, VGA_ATT_W);
				}
				outb_p(0x20, VGA_ATT_W);

				/*
				 * Now set the DAC registers back to their
				 * default values
				 */
				for (i = 0; i < 16; i++) {
					outb_p(color_table[i], VGA_PEL_IW);
					outb_p(default_red[i], VGA_PEL_D);
					outb_p(default_grn[i], VGA_PEL_D);
					outb_p(default_blu[i], VGA_PEL_D);
				}
			}
		} else {
			static struct resource cga_console_resource =
			    { .name	= "cga",
			      .flags	= IORESOURCE_IO,
			      .start	= 0x3D4,
			      .end	= 0x3D5 };
			vga_video_type = VIDEO_TYPE_CGA;
			vga_vram_size = 0x2000;
			display_desc = "*CGA";
			request_resource(&ioport_resource,
					 &cga_console_resource);
			vga_video_font_height = 8;
		}
	}

	vga_vram_base = VGA_MAP_MEM(vga_vram_base, vga_vram_size);
	vga_vram_end = vga_vram_base + vga_vram_size;

	/*
	 *      Find out if there is a graphics card present.
	 *      Are there smarter methods around?
	 */
	p = (volatile u16 *) vga_vram_base;
	saved1 = scr_readw(p);
	saved2 = scr_readw(p + 1);
	scr_writew(0xAA55, p);
	scr_writew(0x55AA, p + 1);
	if (scr_readw(p) != 0xAA55 || scr_readw(p + 1) != 0x55AA) {
		scr_writew(saved1, p);
		scr_writew(saved2, p + 1);
		goto no_vga;
	}
	scr_writew(0x55AA, p);
	scr_writew(0xAA55, p + 1);
	if (scr_readw(p) != 0x55AA || scr_readw(p + 1) != 0xAA55) {
		scr_writew(saved1, p);
		scr_writew(saved2, p + 1);
		goto no_vga;
	}
	scr_writew(saved1, p);
	scr_writew(saved2, p + 1);

	if (vga_video_type == VIDEO_TYPE_EGAC
	    || vga_video_type == VIDEO_TYPE_VGAC
	    || vga_video_type == VIDEO_TYPE_EGAM) {
		vga_hardscroll_enabled = vga_hardscroll_user_enable;
		vga_default_font_height = screen_info.orig_video_points;
		vga_video_font_height = screen_info.orig_video_points;
		/* This may be suboptimal but is a safe bet - go with it */
		vga_scan_lines =
		    vga_video_font_height * vga_video_num_lines;
	}

	vgacon_xres = screen_info.orig_video_cols * VGA_FONTWIDTH;
	vgacon_yres = vga_scan_lines;

	vga_init_done = true;

	return display_desc;
}


static inline void vga_set_mem_top(struct vc_data *c)
{
	write_vga(12, (c->vc_visible_origin - vga_vram_base) / 2);
}


static int vgacon_resize(struct vc_data *c, unsigned int width,
			 unsigned int height, unsigned int user)
{
	if ((width << 1) * height > vga_vram_size)
		return -EINVAL;

	if (width % 2 || width > screen_info.orig_video_cols ||
	    height > (screen_info.orig_video_lines * vga_default_font_height)/
	    c->vc_font.height)
		/* let svgatextmode tinker with video timings and
		   return success */
		return (user) ? 0 : -EINVAL;

	if (con_is_visible(c) && !vga_is_gfx) /* who knows */
		vgacon_doresize(c, width, height);
	return 0;
}


static int vgacon_doresize(struct vc_data *c,
		unsigned int width, unsigned int height)
{
	unsigned long flags;
	unsigned int scanlines = height * c->vc_font.height;
	u8 scanlines_lo = 0, r7 = 0, vsync_end = 0, mode, max_scan;

	raw_spin_lock_irqsave(&vga_lock, flags);

	vgacon_xres = width * VGA_FONTWIDTH;
	vgacon_yres = height * c->vc_font.height;
	if (vga_video_type >= VIDEO_TYPE_VGAC) {
		outb_p(VGA_CRTC_MAX_SCAN, vga_video_port_reg);
		max_scan = inb_p(vga_video_port_val);

		if (max_scan & 0x80)
			scanlines <<= 1;

		outb_p(VGA_CRTC_MODE, vga_video_port_reg);
		mode = inb_p(vga_video_port_val);

		if (mode & 0x04)
			scanlines >>= 1;

		scanlines -= 1;
		scanlines_lo = scanlines & 0xff;

		outb_p(VGA_CRTC_OVERFLOW, vga_video_port_reg);
		r7 = inb_p(vga_video_port_val) & ~0x42;

		if (scanlines & 0x100)
			r7 |= 0x02;
		if (scanlines & 0x200)
			r7 |= 0x40;

		/* deprotect registers */
		outb_p(VGA_CRTC_V_SYNC_END, vga_video_port_reg);
		vsync_end = inb_p(vga_video_port_val);
		outb_p(VGA_CRTC_V_SYNC_END, vga_video_port_reg);
		outb_p(vsync_end & ~0x80, vga_video_port_val);
	}

	outb_p(VGA_CRTC_H_DISP, vga_video_port_reg);
	outb_p(width - 1, vga_video_port_val);
	outb_p(VGA_CRTC_OFFSET, vga_video_port_reg);
	outb_p(width >> 1, vga_video_port_val);

	if (vga_video_type >= VIDEO_TYPE_VGAC) {
		outb_p(VGA_CRTC_V_DISP_END, vga_video_port_reg);
		outb_p(scanlines_lo, vga_video_port_val);
		outb_p(VGA_CRTC_OVERFLOW, vga_video_port_reg);
		outb_p(r7,vga_video_port_val);

		/* reprotect registers */
		outb_p(VGA_CRTC_V_SYNC_END, vga_video_port_reg);
		outb_p(vsync_end, vga_video_port_val);
	}

	raw_spin_unlock_irqrestore(&vga_lock, flags);
	return 0;
}


static int vgacon_switch(struct vc_data *c)
{
	int x = c->vc_cols * VGA_FONTWIDTH;
	int y = c->vc_rows * c->vc_font.height;
	int rows = screen_info.orig_video_lines * vga_default_font_height/
		c->vc_font.height;
	/*
	 * We need to save screen size here as it's the only way
	 * we can spot the screen has been resized and we need to
	 * set size of freshly allocated screens ourselves.
	 */
	vga_video_num_columns = c->vc_cols;
	vga_video_num_lines = c->vc_rows;

	/* We can only copy out the size of the video buffer here,
	 * otherwise we get into VGA BIOS */

	if (!vga_is_gfx) {
		scr_memcpyw((u16 *) c->vc_origin, (u16 *) c->vc_screenbuf,
			    c->vc_screenbuf_size > vga_vram_size ?
				vga_vram_size : c->vc_screenbuf_size);

		if ((vgacon_xres != x || vgacon_yres != y) &&
		    (!(vga_video_num_columns % 2) &&
		     vga_video_num_columns <= screen_info.orig_video_cols &&
		     vga_video_num_lines <= rows))
			vgacon_doresize(c, c->vc_cols, c->vc_rows);
	}

	return 0;		/* Redrawing not needed */
}


static void vgacon_set_cursor_size(int xpos, int from, int to)
{
	unsigned long flags;
	int curs, cure;

	if ((from == cursor_size_lastfrom) && (to == cursor_size_lastto))
		return;
	cursor_size_lastfrom = from;
	cursor_size_lastto = to;

	raw_spin_lock_irqsave(&vga_lock, flags);
	if (vga_video_type >= VIDEO_TYPE_VGAC) {
		outb_p(VGA_CRTC_CURSOR_START, vga_video_port_reg);
		curs = inb_p(vga_video_port_val);
		outb_p(VGA_CRTC_CURSOR_END, vga_video_port_reg);
		cure = inb_p(vga_video_port_val);
	} else {
		curs = 0;
		cure = 0;
	}

	curs = (curs & 0xc0) | from;
	cure = (cure & 0xe0) | to;

	outb_p(VGA_CRTC_CURSOR_START, vga_video_port_reg);
	outb_p(curs, vga_video_port_val);
	outb_p(VGA_CRTC_CURSOR_END, vga_video_port_reg);
	outb_p(cure, vga_video_port_val);
	raw_spin_unlock_irqrestore(&vga_lock, flags);
}


static u8 vgacon_build_attr(struct vc_data *c, u8 color,
			    enum vc_intensity intensity,
			    bool blink, bool underline, bool reverse,
			    bool italic)
{
	u8 attr = color;

	if (vga_can_do_color) {
		if (italic)
			attr = (attr & 0xF0) | c->vc_itcolor;
		else if (underline)
			attr = (attr & 0xf0) | c->vc_ulcolor;
		else if (intensity == VCI_HALF_BRIGHT)
			attr = (attr & 0xf0) | c->vc_halfcolor;
	}
	if (reverse)
		attr =
		    ((attr) & 0x88) | ((((attr) >> 4) | ((attr) << 4)) &
				       0x77);
	if (blink)
		attr ^= 0x80;
	if (intensity == VCI_BOLD)
		attr ^= 0x08;
	if (!vga_can_do_color) {
		if (italic)
			attr = (attr & 0xF8) | 0x02;
		else if (underline)
			attr = (attr & 0xf8) | 0x01;
		else if (intensity == VCI_HALF_BRIGHT)
			attr = (attr & 0xf0) | 0x08;
	}
	return attr;
}


static void vgacon_deinit(struct vc_data *c)
{
	/* When closing the active console, reset video origin */
	if (con_is_visible(c)) {
		c->vc_visible_origin = vga_vram_base;
		vga_set_mem_top(c);
	}

	if (!--vgacon_refcount)
		con_free_unimap(c);
	c->vc_uni_pagedir_loc = &c->vc_uni_pagedir;
	con_set_default_unimap(c);
}


static int vgacon_do_font_op(struct vgastate *state, char *arg, int set,
		bool ch512)
{
	unsigned short video_port_status = vga_video_port_reg + 6;
	int font_select = 0x00, beg, i;
	char *charmap;
	bool clear_attribs = false;
	if (vga_video_type != VIDEO_TYPE_EGAM) {
		charmap = (char *) VGA_MAP_MEM(colourmap, 0);
		beg = 0x0e;
	} else {
		charmap = (char *) VGA_MAP_MEM(blackwmap, 0);
		beg = 0x0a;
	}

#ifdef BROKEN_GRAPHICS_PROGRAMS
	/*
	 * All fonts are loaded in slot 0 (0:1 for 512 ch)
	 */

	if (!arg)
		return -EINVAL;	/* Return to default font not supported */

	vga_font_is_default = false;
	font_select = ch512 ? 0x04 : 0x00;
#else
	/*
	 * The default font is kept in slot 0 and is never touched.
	 * A custom font is loaded in slot 2 (256 ch) or 2:3 (512 ch)
	 */

	if (set) {
		vga_font_is_default = !arg;
		if (!arg)
			ch512 = false;	/* Default font is always 256 */
		font_select = arg ? (ch512 ? 0x0e : 0x0a) : 0x00;
	}

	if (!vga_font_is_default)
		charmap += 4 * cmapsz;
#endif

	raw_spin_lock_irq(&vga_lock);
	/* First, the Sequencer */
	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x1);
	/* CPU writes only to map 2 */
	vga_wseq(state->vgabase, VGA_SEQ_PLANE_WRITE, 0x04);
	/* Sequential addressing */
	vga_wseq(state->vgabase, VGA_SEQ_MEMORY_MODE, 0x07);
	/* Clear synchronous reset */
	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x03);

	/* Now, the graphics controller, select map 2 */
	vga_wgfx(state->vgabase, VGA_GFX_PLANE_READ, 0x02);
	/* disable odd-even addressing */
	vga_wgfx(state->vgabase, VGA_GFX_MODE, 0x00);
	/* map start at A000:0000 */
	vga_wgfx(state->vgabase, VGA_GFX_MISC, 0x00);
	raw_spin_unlock_irq(&vga_lock);

	if (arg) {
		if (set)
			for (i = 0; i < cmapsz; i++) {
				vga_writeb(arg[i], charmap + i);
				cond_resched();
			}
		else
			for (i = 0; i < cmapsz; i++) {
				arg[i] = vga_readb(charmap + i);
				cond_resched();
			}

		/*
		 * In 512-character mode, the character map is not contiguous if
		 * we want to remain EGA compatible -- which we do
		 */

		if (ch512) {
			charmap += 2 * cmapsz;
			arg += cmapsz;
			if (set)
				for (i = 0; i < cmapsz; i++) {
					vga_writeb(arg[i], charmap + i);
					cond_resched();
				}
			else
				for (i = 0; i < cmapsz; i++) {
					arg[i] = vga_readb(charmap + i);
					cond_resched();
				}
		}
	}

	raw_spin_lock_irq(&vga_lock);
	/* First, the sequencer, Synchronous reset */
	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x01);
	/* CPU writes to maps 0 and 1 */
	vga_wseq(state->vgabase, VGA_SEQ_PLANE_WRITE, 0x03);
	/* odd-even addressing */
	vga_wseq(state->vgabase, VGA_SEQ_MEMORY_MODE, 0x03);
	/* Character Map Select */
	if (set)
		vga_wseq(state->vgabase, VGA_SEQ_CHARACTER_MAP, font_select);
	/* clear synchronous reset */
	vga_wseq(state->vgabase, VGA_SEQ_RESET, 0x03);

	/* Now, the graphics controller, select map 0 for CPU */
	vga_wgfx(state->vgabase, VGA_GFX_PLANE_READ, 0x00);
	/* enable even-odd addressing */
	vga_wgfx(state->vgabase, VGA_GFX_MODE, 0x10);
	/* map starts at b800:0 or b000:0 */
	vga_wgfx(state->vgabase, VGA_GFX_MISC, beg);

	/* if 512 char mode is already enabled don't re-enable it. */
	if ((set) && (ch512 != vga_512_chars)) {
		vga_512_chars = ch512;
		/* 256-char: enable intensity bit
		   512-char: disable intensity bit */
		inb_p(video_port_status);	/* clear address flip-flop */
		/* color plane enable register */
		vga_wattr(state->vgabase, VGA_ATC_PLANE_ENABLE, ch512 ? 0x07 : 0x0f);
		/* Wilton (1987) mentions the following; I don't know what
		   it means, but it works, and it appears necessary */
		inb_p(video_port_status);
		vga_wattr(state->vgabase, VGA_AR_ENABLE_DISPLAY, 0);
		clear_attribs = true;
	}
	raw_spin_unlock_irq(&vga_lock);

	if (clear_attribs) {
		for (i = 0; i < MAX_NR_CONSOLES; i++) {
			struct vc_data *c = vc_cons[i].d;
			if (c && c->vc_sw == &vga_con) {
				/* force hi font mask to 0, so we always clear
				   the bit on either transition */
				c->vc_hi_font_mask = 0x00;
				clear_buffer_attributes(c);
				c->vc_hi_font_mask = ch512 ? 0x0800 : 0;
			}
		}
	}
	return 0;
}


static void vga_vesa_blank(struct vgastate *state, int mode)
{
	/* save original values of VGA controller registers */
	if (!vga_vesa_blanked) {
		raw_spin_lock_irq(&vga_lock);
		vga_state.SeqCtrlIndex = vga_r(state->vgabase, VGA_SEQ_I);
		vga_state.CrtCtrlIndex = inb_p(vga_video_port_reg);
		vga_state.CrtMiscIO = vga_r(state->vgabase, VGA_MIS_R);
		raw_spin_unlock_irq(&vga_lock);

		outb_p(0x00, vga_video_port_reg);	/* HorizontalTotal */
		vga_state.HorizontalTotal = inb_p(vga_video_port_val);
		outb_p(0x01, vga_video_port_reg);	/* HorizDisplayEnd */
		vga_state.HorizDisplayEnd = inb_p(vga_video_port_val);
		outb_p(0x04, vga_video_port_reg);	/* StartHorizRetrace */
		vga_state.StartHorizRetrace = inb_p(vga_video_port_val);
		outb_p(0x05, vga_video_port_reg);	/* EndHorizRetrace */
		vga_state.EndHorizRetrace = inb_p(vga_video_port_val);
		outb_p(0x07, vga_video_port_reg);	/* Overflow */
		vga_state.Overflow = inb_p(vga_video_port_val);
		outb_p(0x10, vga_video_port_reg);	/* StartVertRetrace */
		vga_state.StartVertRetrace = inb_p(vga_video_port_val);
		outb_p(0x11, vga_video_port_reg);	/* EndVertRetrace */
		vga_state.EndVertRetrace = inb_p(vga_video_port_val);
		outb_p(0x17, vga_video_port_reg);	/* ModeControl */
		vga_state.ModeControl = inb_p(vga_video_port_val);
		vga_state.ClockingMode = vga_rseq(state->vgabase, VGA_SEQ_CLOCK_MODE);
	}

	/* assure that video is enabled */
	/* "0x20" is VIDEO_ENABLE_bit in register 01 of sequencer */
	raw_spin_lock_irq(&vga_lock);
	vga_wseq(state->vgabase, VGA_SEQ_CLOCK_MODE, vga_state.ClockingMode | 0x20);

	/* test for vertical retrace in process.... */
	if ((vga_state.CrtMiscIO & 0x80) == 0x80)
		vga_w(state->vgabase, VGA_MIS_W, vga_state.CrtMiscIO & 0xEF);

	/*
	 * Set <End of vertical retrace> to minimum (0) and
	 * <Start of vertical Retrace> to maximum (incl. overflow)
	 * Result: turn off vertical sync (VSync) pulse.
	 */
	if (mode & VESA_VSYNC_SUSPEND) {
		outb_p(0x10, vga_video_port_reg);	/* StartVertRetrace */
		outb_p(0xff, vga_video_port_val);	/* maximum value */
		outb_p(0x11, vga_video_port_reg);	/* EndVertRetrace */
		outb_p(0x40, vga_video_port_val);	/* minimum (bits 0..3)  */
		outb_p(0x07, vga_video_port_reg);	/* Overflow */
		outb_p(vga_state.Overflow | 0x84, vga_video_port_val);	/* bits 9,10 of vert. retrace */
	}

	if (mode & VESA_HSYNC_SUSPEND) {
		/*
		 * Set <End of horizontal retrace> to minimum (0) and
		 *  <Start of horizontal Retrace> to maximum
		 * Result: turn off horizontal sync (HSync) pulse.
		 */
		outb_p(0x04, vga_video_port_reg);	/* StartHorizRetrace */
		outb_p(0xff, vga_video_port_val);	/* maximum */
		outb_p(0x05, vga_video_port_reg);	/* EndHorizRetrace */
		outb_p(0x00, vga_video_port_val);	/* minimum (0) */
	}

	/* restore both index registers */
	vga_w(state->vgabase, VGA_SEQ_I, vga_state.SeqCtrlIndex);
	outb_p(vga_state.CrtCtrlIndex, vga_video_port_reg);
	raw_spin_unlock_irq(&vga_lock);
}