162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ccw based virtio transport
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright IBM Corp. 2012, 2014
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *    Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel_stat.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/memblock.h>
1362306a36Sopenharmony_ci#include <linux/err.h>
1462306a36Sopenharmony_ci#include <linux/virtio.h>
1562306a36Sopenharmony_ci#include <linux/virtio_config.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci#include <linux/interrupt.h>
1862306a36Sopenharmony_ci#include <linux/virtio_ring.h>
1962306a36Sopenharmony_ci#include <linux/pfn.h>
2062306a36Sopenharmony_ci#include <linux/async.h>
2162306a36Sopenharmony_ci#include <linux/wait.h>
2262306a36Sopenharmony_ci#include <linux/list.h>
2362306a36Sopenharmony_ci#include <linux/bitops.h>
2462306a36Sopenharmony_ci#include <linux/moduleparam.h>
2562306a36Sopenharmony_ci#include <linux/io.h>
2662306a36Sopenharmony_ci#include <linux/kvm_para.h>
2762306a36Sopenharmony_ci#include <linux/notifier.h>
2862306a36Sopenharmony_ci#include <asm/diag.h>
2962306a36Sopenharmony_ci#include <asm/setup.h>
3062306a36Sopenharmony_ci#include <asm/irq.h>
3162306a36Sopenharmony_ci#include <asm/cio.h>
3262306a36Sopenharmony_ci#include <asm/ccwdev.h>
3362306a36Sopenharmony_ci#include <asm/virtio-ccw.h>
3462306a36Sopenharmony_ci#include <asm/isc.h>
3562306a36Sopenharmony_ci#include <asm/airq.h>
3662306a36Sopenharmony_ci#include <asm/tpi.h>
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/*
3962306a36Sopenharmony_ci * virtio related functions
4062306a36Sopenharmony_ci */
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct vq_config_block {
4362306a36Sopenharmony_ci	__u16 index;
4462306a36Sopenharmony_ci	__u16 num;
4562306a36Sopenharmony_ci} __packed;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define VIRTIO_CCW_CONFIG_SIZE 0x100
4862306a36Sopenharmony_ci/* same as PCI config space size, should be enough for all drivers */
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistruct vcdev_dma_area {
5162306a36Sopenharmony_ci	unsigned long indicators;
5262306a36Sopenharmony_ci	unsigned long indicators2;
5362306a36Sopenharmony_ci	struct vq_config_block config_block;
5462306a36Sopenharmony_ci	__u8 status;
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistruct virtio_ccw_device {
5862306a36Sopenharmony_ci	struct virtio_device vdev;
5962306a36Sopenharmony_ci	__u8 config[VIRTIO_CCW_CONFIG_SIZE];
6062306a36Sopenharmony_ci	struct ccw_device *cdev;
6162306a36Sopenharmony_ci	__u32 curr_io;
6262306a36Sopenharmony_ci	int err;
6362306a36Sopenharmony_ci	unsigned int revision; /* Transport revision */
6462306a36Sopenharmony_ci	wait_queue_head_t wait_q;
6562306a36Sopenharmony_ci	spinlock_t lock;
6662306a36Sopenharmony_ci	rwlock_t irq_lock;
6762306a36Sopenharmony_ci	struct mutex io_lock; /* Serializes I/O requests */
6862306a36Sopenharmony_ci	struct list_head virtqueues;
6962306a36Sopenharmony_ci	bool is_thinint;
7062306a36Sopenharmony_ci	bool going_away;
7162306a36Sopenharmony_ci	bool device_lost;
7262306a36Sopenharmony_ci	unsigned int config_ready;
7362306a36Sopenharmony_ci	void *airq_info;
7462306a36Sopenharmony_ci	struct vcdev_dma_area *dma_area;
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic inline unsigned long *indicators(struct virtio_ccw_device *vcdev)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	return &vcdev->dma_area->indicators;
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic inline unsigned long *indicators2(struct virtio_ccw_device *vcdev)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	return &vcdev->dma_area->indicators2;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistruct vq_info_block_legacy {
8862306a36Sopenharmony_ci	__u64 queue;
8962306a36Sopenharmony_ci	__u32 align;
9062306a36Sopenharmony_ci	__u16 index;
9162306a36Sopenharmony_ci	__u16 num;
9262306a36Sopenharmony_ci} __packed;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistruct vq_info_block {
9562306a36Sopenharmony_ci	__u64 desc;
9662306a36Sopenharmony_ci	__u32 res0;
9762306a36Sopenharmony_ci	__u16 index;
9862306a36Sopenharmony_ci	__u16 num;
9962306a36Sopenharmony_ci	__u64 avail;
10062306a36Sopenharmony_ci	__u64 used;
10162306a36Sopenharmony_ci} __packed;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistruct virtio_feature_desc {
10462306a36Sopenharmony_ci	__le32 features;
10562306a36Sopenharmony_ci	__u8 index;
10662306a36Sopenharmony_ci} __packed;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistruct virtio_thinint_area {
10962306a36Sopenharmony_ci	unsigned long summary_indicator;
11062306a36Sopenharmony_ci	unsigned long indicator;
11162306a36Sopenharmony_ci	u64 bit_nr;
11262306a36Sopenharmony_ci	u8 isc;
11362306a36Sopenharmony_ci} __packed;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistruct virtio_rev_info {
11662306a36Sopenharmony_ci	__u16 revision;
11762306a36Sopenharmony_ci	__u16 length;
11862306a36Sopenharmony_ci	__u8 data[];
11962306a36Sopenharmony_ci};
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci/* the highest virtio-ccw revision we support */
12262306a36Sopenharmony_ci#define VIRTIO_CCW_REV_MAX 2
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistruct virtio_ccw_vq_info {
12562306a36Sopenharmony_ci	struct virtqueue *vq;
12662306a36Sopenharmony_ci	int num;
12762306a36Sopenharmony_ci	union {
12862306a36Sopenharmony_ci		struct vq_info_block s;
12962306a36Sopenharmony_ci		struct vq_info_block_legacy l;
13062306a36Sopenharmony_ci	} *info_block;
13162306a36Sopenharmony_ci	int bit_nr;
13262306a36Sopenharmony_ci	struct list_head node;
13362306a36Sopenharmony_ci	long cookie;
13462306a36Sopenharmony_ci};
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci#define VIRTIO_AIRQ_ISC IO_SCH_ISC /* inherit from subchannel */
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci#define VIRTIO_IV_BITS (L1_CACHE_BYTES * 8)
13962306a36Sopenharmony_ci#define MAX_AIRQ_AREAS 20
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic int virtio_ccw_use_airq = 1;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistruct airq_info {
14462306a36Sopenharmony_ci	rwlock_t lock;
14562306a36Sopenharmony_ci	u8 summary_indicator_idx;
14662306a36Sopenharmony_ci	struct airq_struct airq;
14762306a36Sopenharmony_ci	struct airq_iv *aiv;
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_cistatic struct airq_info *airq_areas[MAX_AIRQ_AREAS];
15062306a36Sopenharmony_cistatic DEFINE_MUTEX(airq_areas_lock);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic u8 *summary_indicators;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic inline u8 *get_summary_indicator(struct airq_info *info)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	return summary_indicators + info->summary_indicator_idx;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci#define CCW_CMD_SET_VQ 0x13
16062306a36Sopenharmony_ci#define CCW_CMD_VDEV_RESET 0x33
16162306a36Sopenharmony_ci#define CCW_CMD_SET_IND 0x43
16262306a36Sopenharmony_ci#define CCW_CMD_SET_CONF_IND 0x53
16362306a36Sopenharmony_ci#define CCW_CMD_READ_FEAT 0x12
16462306a36Sopenharmony_ci#define CCW_CMD_WRITE_FEAT 0x11
16562306a36Sopenharmony_ci#define CCW_CMD_READ_CONF 0x22
16662306a36Sopenharmony_ci#define CCW_CMD_WRITE_CONF 0x21
16762306a36Sopenharmony_ci#define CCW_CMD_WRITE_STATUS 0x31
16862306a36Sopenharmony_ci#define CCW_CMD_READ_VQ_CONF 0x32
16962306a36Sopenharmony_ci#define CCW_CMD_READ_STATUS 0x72
17062306a36Sopenharmony_ci#define CCW_CMD_SET_IND_ADAPTER 0x73
17162306a36Sopenharmony_ci#define CCW_CMD_SET_VIRTIO_REV 0x83
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_SET_VQ 0x00010000
17462306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_RESET 0x00040000
17562306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_READ_FEAT 0x00080000
17662306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_WRITE_FEAT 0x00100000
17762306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_READ_CONFIG 0x00200000
17862306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_WRITE_CONFIG 0x00400000
17962306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_WRITE_STATUS 0x00800000
18062306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_SET_IND 0x01000000
18162306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_READ_VQ_CONF 0x02000000
18262306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_SET_CONF_IND 0x04000000
18362306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_SET_IND_ADAPTER 0x08000000
18462306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_SET_VIRTIO_REV 0x10000000
18562306a36Sopenharmony_ci#define VIRTIO_CCW_DOING_READ_STATUS 0x20000000
18662306a36Sopenharmony_ci#define VIRTIO_CCW_INTPARM_MASK 0xffff0000
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic struct virtio_ccw_device *to_vc_device(struct virtio_device *vdev)
18962306a36Sopenharmony_ci{
19062306a36Sopenharmony_ci	return container_of(vdev, struct virtio_ccw_device, vdev);
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic void drop_airq_indicator(struct virtqueue *vq, struct airq_info *info)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	unsigned long i, flags;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	write_lock_irqsave(&info->lock, flags);
19862306a36Sopenharmony_ci	for (i = 0; i < airq_iv_end(info->aiv); i++) {
19962306a36Sopenharmony_ci		if (vq == (void *)airq_iv_get_ptr(info->aiv, i)) {
20062306a36Sopenharmony_ci			airq_iv_free_bit(info->aiv, i);
20162306a36Sopenharmony_ci			airq_iv_set_ptr(info->aiv, i, 0);
20262306a36Sopenharmony_ci			break;
20362306a36Sopenharmony_ci		}
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci	write_unlock_irqrestore(&info->lock, flags);
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic void virtio_airq_handler(struct airq_struct *airq,
20962306a36Sopenharmony_ci				struct tpi_info *tpi_info)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	struct airq_info *info = container_of(airq, struct airq_info, airq);
21262306a36Sopenharmony_ci	unsigned long ai;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	inc_irq_stat(IRQIO_VAI);
21562306a36Sopenharmony_ci	read_lock(&info->lock);
21662306a36Sopenharmony_ci	/* Walk through indicators field, summary indicator active. */
21762306a36Sopenharmony_ci	for (ai = 0;;) {
21862306a36Sopenharmony_ci		ai = airq_iv_scan(info->aiv, ai, airq_iv_end(info->aiv));
21962306a36Sopenharmony_ci		if (ai == -1UL)
22062306a36Sopenharmony_ci			break;
22162306a36Sopenharmony_ci		vring_interrupt(0, (void *)airq_iv_get_ptr(info->aiv, ai));
22262306a36Sopenharmony_ci	}
22362306a36Sopenharmony_ci	*(get_summary_indicator(info)) = 0;
22462306a36Sopenharmony_ci	smp_wmb();
22562306a36Sopenharmony_ci	/* Walk through indicators field, summary indicator not active. */
22662306a36Sopenharmony_ci	for (ai = 0;;) {
22762306a36Sopenharmony_ci		ai = airq_iv_scan(info->aiv, ai, airq_iv_end(info->aiv));
22862306a36Sopenharmony_ci		if (ai == -1UL)
22962306a36Sopenharmony_ci			break;
23062306a36Sopenharmony_ci		vring_interrupt(0, (void *)airq_iv_get_ptr(info->aiv, ai));
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci	read_unlock(&info->lock);
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic struct airq_info *new_airq_info(int index)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	struct airq_info *info;
23862306a36Sopenharmony_ci	int rc;
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	info = kzalloc(sizeof(*info), GFP_KERNEL);
24162306a36Sopenharmony_ci	if (!info)
24262306a36Sopenharmony_ci		return NULL;
24362306a36Sopenharmony_ci	rwlock_init(&info->lock);
24462306a36Sopenharmony_ci	info->aiv = airq_iv_create(VIRTIO_IV_BITS, AIRQ_IV_ALLOC | AIRQ_IV_PTR
24562306a36Sopenharmony_ci				   | AIRQ_IV_CACHELINE, NULL);
24662306a36Sopenharmony_ci	if (!info->aiv) {
24762306a36Sopenharmony_ci		kfree(info);
24862306a36Sopenharmony_ci		return NULL;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci	info->airq.handler = virtio_airq_handler;
25162306a36Sopenharmony_ci	info->summary_indicator_idx = index;
25262306a36Sopenharmony_ci	info->airq.lsi_ptr = get_summary_indicator(info);
25362306a36Sopenharmony_ci	info->airq.isc = VIRTIO_AIRQ_ISC;
25462306a36Sopenharmony_ci	rc = register_adapter_interrupt(&info->airq);
25562306a36Sopenharmony_ci	if (rc) {
25662306a36Sopenharmony_ci		airq_iv_release(info->aiv);
25762306a36Sopenharmony_ci		kfree(info);
25862306a36Sopenharmony_ci		return NULL;
25962306a36Sopenharmony_ci	}
26062306a36Sopenharmony_ci	return info;
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic unsigned long get_airq_indicator(struct virtqueue *vqs[], int nvqs,
26462306a36Sopenharmony_ci					u64 *first, void **airq_info)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	int i, j;
26762306a36Sopenharmony_ci	struct airq_info *info;
26862306a36Sopenharmony_ci	unsigned long indicator_addr = 0;
26962306a36Sopenharmony_ci	unsigned long bit, flags;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	for (i = 0; i < MAX_AIRQ_AREAS && !indicator_addr; i++) {
27262306a36Sopenharmony_ci		mutex_lock(&airq_areas_lock);
27362306a36Sopenharmony_ci		if (!airq_areas[i])
27462306a36Sopenharmony_ci			airq_areas[i] = new_airq_info(i);
27562306a36Sopenharmony_ci		info = airq_areas[i];
27662306a36Sopenharmony_ci		mutex_unlock(&airq_areas_lock);
27762306a36Sopenharmony_ci		if (!info)
27862306a36Sopenharmony_ci			return 0;
27962306a36Sopenharmony_ci		write_lock_irqsave(&info->lock, flags);
28062306a36Sopenharmony_ci		bit = airq_iv_alloc(info->aiv, nvqs);
28162306a36Sopenharmony_ci		if (bit == -1UL) {
28262306a36Sopenharmony_ci			/* Not enough vacancies. */
28362306a36Sopenharmony_ci			write_unlock_irqrestore(&info->lock, flags);
28462306a36Sopenharmony_ci			continue;
28562306a36Sopenharmony_ci		}
28662306a36Sopenharmony_ci		*first = bit;
28762306a36Sopenharmony_ci		*airq_info = info;
28862306a36Sopenharmony_ci		indicator_addr = (unsigned long)info->aiv->vector;
28962306a36Sopenharmony_ci		for (j = 0; j < nvqs; j++) {
29062306a36Sopenharmony_ci			airq_iv_set_ptr(info->aiv, bit + j,
29162306a36Sopenharmony_ci					(unsigned long)vqs[j]);
29262306a36Sopenharmony_ci		}
29362306a36Sopenharmony_ci		write_unlock_irqrestore(&info->lock, flags);
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci	return indicator_addr;
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic void virtio_ccw_drop_indicators(struct virtio_ccw_device *vcdev)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	struct virtio_ccw_vq_info *info;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	if (!vcdev->airq_info)
30362306a36Sopenharmony_ci		return;
30462306a36Sopenharmony_ci	list_for_each_entry(info, &vcdev->virtqueues, node)
30562306a36Sopenharmony_ci		drop_airq_indicator(info->vq, vcdev->airq_info);
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic int doing_io(struct virtio_ccw_device *vcdev, __u32 flag)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	unsigned long flags;
31162306a36Sopenharmony_ci	__u32 ret;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	spin_lock_irqsave(get_ccwdev_lock(vcdev->cdev), flags);
31462306a36Sopenharmony_ci	if (vcdev->err)
31562306a36Sopenharmony_ci		ret = 0;
31662306a36Sopenharmony_ci	else
31762306a36Sopenharmony_ci		ret = vcdev->curr_io & flag;
31862306a36Sopenharmony_ci	spin_unlock_irqrestore(get_ccwdev_lock(vcdev->cdev), flags);
31962306a36Sopenharmony_ci	return ret;
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic int ccw_io_helper(struct virtio_ccw_device *vcdev,
32362306a36Sopenharmony_ci			 struct ccw1 *ccw, __u32 intparm)
32462306a36Sopenharmony_ci{
32562306a36Sopenharmony_ci	int ret;
32662306a36Sopenharmony_ci	unsigned long flags;
32762306a36Sopenharmony_ci	int flag = intparm & VIRTIO_CCW_INTPARM_MASK;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	mutex_lock(&vcdev->io_lock);
33062306a36Sopenharmony_ci	do {
33162306a36Sopenharmony_ci		spin_lock_irqsave(get_ccwdev_lock(vcdev->cdev), flags);
33262306a36Sopenharmony_ci		ret = ccw_device_start(vcdev->cdev, ccw, intparm, 0, 0);
33362306a36Sopenharmony_ci		if (!ret) {
33462306a36Sopenharmony_ci			if (!vcdev->curr_io)
33562306a36Sopenharmony_ci				vcdev->err = 0;
33662306a36Sopenharmony_ci			vcdev->curr_io |= flag;
33762306a36Sopenharmony_ci		}
33862306a36Sopenharmony_ci		spin_unlock_irqrestore(get_ccwdev_lock(vcdev->cdev), flags);
33962306a36Sopenharmony_ci		cpu_relax();
34062306a36Sopenharmony_ci	} while (ret == -EBUSY);
34162306a36Sopenharmony_ci	wait_event(vcdev->wait_q, doing_io(vcdev, flag) == 0);
34262306a36Sopenharmony_ci	ret = ret ? ret : vcdev->err;
34362306a36Sopenharmony_ci	mutex_unlock(&vcdev->io_lock);
34462306a36Sopenharmony_ci	return ret;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic void virtio_ccw_drop_indicator(struct virtio_ccw_device *vcdev,
34862306a36Sopenharmony_ci				      struct ccw1 *ccw)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	int ret;
35162306a36Sopenharmony_ci	unsigned long *indicatorp = NULL;
35262306a36Sopenharmony_ci	struct virtio_thinint_area *thinint_area = NULL;
35362306a36Sopenharmony_ci	struct airq_info *airq_info = vcdev->airq_info;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	if (vcdev->is_thinint) {
35662306a36Sopenharmony_ci		thinint_area = ccw_device_dma_zalloc(vcdev->cdev,
35762306a36Sopenharmony_ci						     sizeof(*thinint_area));
35862306a36Sopenharmony_ci		if (!thinint_area)
35962306a36Sopenharmony_ci			return;
36062306a36Sopenharmony_ci		thinint_area->summary_indicator =
36162306a36Sopenharmony_ci			(unsigned long) get_summary_indicator(airq_info);
36262306a36Sopenharmony_ci		thinint_area->isc = VIRTIO_AIRQ_ISC;
36362306a36Sopenharmony_ci		ccw->cmd_code = CCW_CMD_SET_IND_ADAPTER;
36462306a36Sopenharmony_ci		ccw->count = sizeof(*thinint_area);
36562306a36Sopenharmony_ci		ccw->cda = (__u32)virt_to_phys(thinint_area);
36662306a36Sopenharmony_ci	} else {
36762306a36Sopenharmony_ci		/* payload is the address of the indicators */
36862306a36Sopenharmony_ci		indicatorp = ccw_device_dma_zalloc(vcdev->cdev,
36962306a36Sopenharmony_ci						   sizeof(indicators(vcdev)));
37062306a36Sopenharmony_ci		if (!indicatorp)
37162306a36Sopenharmony_ci			return;
37262306a36Sopenharmony_ci		*indicatorp = 0;
37362306a36Sopenharmony_ci		ccw->cmd_code = CCW_CMD_SET_IND;
37462306a36Sopenharmony_ci		ccw->count = sizeof(indicators(vcdev));
37562306a36Sopenharmony_ci		ccw->cda = (__u32)virt_to_phys(indicatorp);
37662306a36Sopenharmony_ci	}
37762306a36Sopenharmony_ci	/* Deregister indicators from host. */
37862306a36Sopenharmony_ci	*indicators(vcdev) = 0;
37962306a36Sopenharmony_ci	ccw->flags = 0;
38062306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw,
38162306a36Sopenharmony_ci			    vcdev->is_thinint ?
38262306a36Sopenharmony_ci			    VIRTIO_CCW_DOING_SET_IND_ADAPTER :
38362306a36Sopenharmony_ci			    VIRTIO_CCW_DOING_SET_IND);
38462306a36Sopenharmony_ci	if (ret && (ret != -ENODEV))
38562306a36Sopenharmony_ci		dev_info(&vcdev->cdev->dev,
38662306a36Sopenharmony_ci			 "Failed to deregister indicators (%d)\n", ret);
38762306a36Sopenharmony_ci	else if (vcdev->is_thinint)
38862306a36Sopenharmony_ci		virtio_ccw_drop_indicators(vcdev);
38962306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, indicatorp, sizeof(indicators(vcdev)));
39062306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, thinint_area, sizeof(*thinint_area));
39162306a36Sopenharmony_ci}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic inline bool virtio_ccw_do_kvm_notify(struct virtqueue *vq, u32 data)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	struct virtio_ccw_vq_info *info = vq->priv;
39662306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev;
39762306a36Sopenharmony_ci	struct subchannel_id schid;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	vcdev = to_vc_device(info->vq->vdev);
40062306a36Sopenharmony_ci	ccw_device_get_schid(vcdev->cdev, &schid);
40162306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct subchannel_id) != sizeof(unsigned int));
40262306a36Sopenharmony_ci	info->cookie = kvm_hypercall3(KVM_S390_VIRTIO_CCW_NOTIFY,
40362306a36Sopenharmony_ci				      *((unsigned int *)&schid),
40462306a36Sopenharmony_ci				      data, info->cookie);
40562306a36Sopenharmony_ci	if (info->cookie < 0)
40662306a36Sopenharmony_ci		return false;
40762306a36Sopenharmony_ci	return true;
40862306a36Sopenharmony_ci}
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_cistatic bool virtio_ccw_kvm_notify(struct virtqueue *vq)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	return virtio_ccw_do_kvm_notify(vq, vq->index);
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic bool virtio_ccw_kvm_notify_with_data(struct virtqueue *vq)
41662306a36Sopenharmony_ci{
41762306a36Sopenharmony_ci	return virtio_ccw_do_kvm_notify(vq, vring_notification_data(vq));
41862306a36Sopenharmony_ci}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_cistatic int virtio_ccw_read_vq_conf(struct virtio_ccw_device *vcdev,
42162306a36Sopenharmony_ci				   struct ccw1 *ccw, int index)
42262306a36Sopenharmony_ci{
42362306a36Sopenharmony_ci	int ret;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	vcdev->dma_area->config_block.index = index;
42662306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_READ_VQ_CONF;
42762306a36Sopenharmony_ci	ccw->flags = 0;
42862306a36Sopenharmony_ci	ccw->count = sizeof(struct vq_config_block);
42962306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(&vcdev->dma_area->config_block);
43062306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_READ_VQ_CONF);
43162306a36Sopenharmony_ci	if (ret)
43262306a36Sopenharmony_ci		return ret;
43362306a36Sopenharmony_ci	return vcdev->dma_area->config_block.num ?: -ENOENT;
43462306a36Sopenharmony_ci}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_cistatic void virtio_ccw_del_vq(struct virtqueue *vq, struct ccw1 *ccw)
43762306a36Sopenharmony_ci{
43862306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vq->vdev);
43962306a36Sopenharmony_ci	struct virtio_ccw_vq_info *info = vq->priv;
44062306a36Sopenharmony_ci	unsigned long flags;
44162306a36Sopenharmony_ci	int ret;
44262306a36Sopenharmony_ci	unsigned int index = vq->index;
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	/* Remove from our list. */
44562306a36Sopenharmony_ci	spin_lock_irqsave(&vcdev->lock, flags);
44662306a36Sopenharmony_ci	list_del(&info->node);
44762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vcdev->lock, flags);
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	/* Release from host. */
45062306a36Sopenharmony_ci	if (vcdev->revision == 0) {
45162306a36Sopenharmony_ci		info->info_block->l.queue = 0;
45262306a36Sopenharmony_ci		info->info_block->l.align = 0;
45362306a36Sopenharmony_ci		info->info_block->l.index = index;
45462306a36Sopenharmony_ci		info->info_block->l.num = 0;
45562306a36Sopenharmony_ci		ccw->count = sizeof(info->info_block->l);
45662306a36Sopenharmony_ci	} else {
45762306a36Sopenharmony_ci		info->info_block->s.desc = 0;
45862306a36Sopenharmony_ci		info->info_block->s.index = index;
45962306a36Sopenharmony_ci		info->info_block->s.num = 0;
46062306a36Sopenharmony_ci		info->info_block->s.avail = 0;
46162306a36Sopenharmony_ci		info->info_block->s.used = 0;
46262306a36Sopenharmony_ci		ccw->count = sizeof(info->info_block->s);
46362306a36Sopenharmony_ci	}
46462306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_SET_VQ;
46562306a36Sopenharmony_ci	ccw->flags = 0;
46662306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(info->info_block);
46762306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw,
46862306a36Sopenharmony_ci			    VIRTIO_CCW_DOING_SET_VQ | index);
46962306a36Sopenharmony_ci	/*
47062306a36Sopenharmony_ci	 * -ENODEV isn't considered an error: The device is gone anyway.
47162306a36Sopenharmony_ci	 * This may happen on device detach.
47262306a36Sopenharmony_ci	 */
47362306a36Sopenharmony_ci	if (ret && (ret != -ENODEV))
47462306a36Sopenharmony_ci		dev_warn(&vq->vdev->dev, "Error %d while deleting queue %d\n",
47562306a36Sopenharmony_ci			 ret, index);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	vring_del_virtqueue(vq);
47862306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, info->info_block,
47962306a36Sopenharmony_ci			    sizeof(*info->info_block));
48062306a36Sopenharmony_ci	kfree(info);
48162306a36Sopenharmony_ci}
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_cistatic void virtio_ccw_del_vqs(struct virtio_device *vdev)
48462306a36Sopenharmony_ci{
48562306a36Sopenharmony_ci	struct virtqueue *vq, *n;
48662306a36Sopenharmony_ci	struct ccw1 *ccw;
48762306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
49062306a36Sopenharmony_ci	if (!ccw)
49162306a36Sopenharmony_ci		return;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	virtio_ccw_drop_indicator(vcdev, ccw);
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	list_for_each_entry_safe(vq, n, &vdev->vqs, list)
49662306a36Sopenharmony_ci		virtio_ccw_del_vq(vq, ccw);
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
49962306a36Sopenharmony_ci}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_cistatic struct virtqueue *virtio_ccw_setup_vq(struct virtio_device *vdev,
50262306a36Sopenharmony_ci					     int i, vq_callback_t *callback,
50362306a36Sopenharmony_ci					     const char *name, bool ctx,
50462306a36Sopenharmony_ci					     struct ccw1 *ccw)
50562306a36Sopenharmony_ci{
50662306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
50762306a36Sopenharmony_ci	bool (*notify)(struct virtqueue *vq);
50862306a36Sopenharmony_ci	int err;
50962306a36Sopenharmony_ci	struct virtqueue *vq = NULL;
51062306a36Sopenharmony_ci	struct virtio_ccw_vq_info *info;
51162306a36Sopenharmony_ci	u64 queue;
51262306a36Sopenharmony_ci	unsigned long flags;
51362306a36Sopenharmony_ci	bool may_reduce;
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	if (__virtio_test_bit(vdev, VIRTIO_F_NOTIFICATION_DATA))
51662306a36Sopenharmony_ci		notify = virtio_ccw_kvm_notify_with_data;
51762306a36Sopenharmony_ci	else
51862306a36Sopenharmony_ci		notify = virtio_ccw_kvm_notify;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	/* Allocate queue. */
52162306a36Sopenharmony_ci	info = kzalloc(sizeof(struct virtio_ccw_vq_info), GFP_KERNEL);
52262306a36Sopenharmony_ci	if (!info) {
52362306a36Sopenharmony_ci		dev_warn(&vcdev->cdev->dev, "no info\n");
52462306a36Sopenharmony_ci		err = -ENOMEM;
52562306a36Sopenharmony_ci		goto out_err;
52662306a36Sopenharmony_ci	}
52762306a36Sopenharmony_ci	info->info_block = ccw_device_dma_zalloc(vcdev->cdev,
52862306a36Sopenharmony_ci						 sizeof(*info->info_block));
52962306a36Sopenharmony_ci	if (!info->info_block) {
53062306a36Sopenharmony_ci		dev_warn(&vcdev->cdev->dev, "no info block\n");
53162306a36Sopenharmony_ci		err = -ENOMEM;
53262306a36Sopenharmony_ci		goto out_err;
53362306a36Sopenharmony_ci	}
53462306a36Sopenharmony_ci	info->num = virtio_ccw_read_vq_conf(vcdev, ccw, i);
53562306a36Sopenharmony_ci	if (info->num < 0) {
53662306a36Sopenharmony_ci		err = info->num;
53762306a36Sopenharmony_ci		goto out_err;
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci	may_reduce = vcdev->revision > 0;
54062306a36Sopenharmony_ci	vq = vring_create_virtqueue(i, info->num, KVM_VIRTIO_CCW_RING_ALIGN,
54162306a36Sopenharmony_ci				    vdev, true, may_reduce, ctx,
54262306a36Sopenharmony_ci				    notify, callback, name);
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	if (!vq) {
54562306a36Sopenharmony_ci		/* For now, we fail if we can't get the requested size. */
54662306a36Sopenharmony_ci		dev_warn(&vcdev->cdev->dev, "no vq\n");
54762306a36Sopenharmony_ci		err = -ENOMEM;
54862306a36Sopenharmony_ci		goto out_err;
54962306a36Sopenharmony_ci	}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci	vq->num_max = info->num;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	/* it may have been reduced */
55462306a36Sopenharmony_ci	info->num = virtqueue_get_vring_size(vq);
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	/* Register it with the host. */
55762306a36Sopenharmony_ci	queue = virtqueue_get_desc_addr(vq);
55862306a36Sopenharmony_ci	if (vcdev->revision == 0) {
55962306a36Sopenharmony_ci		info->info_block->l.queue = queue;
56062306a36Sopenharmony_ci		info->info_block->l.align = KVM_VIRTIO_CCW_RING_ALIGN;
56162306a36Sopenharmony_ci		info->info_block->l.index = i;
56262306a36Sopenharmony_ci		info->info_block->l.num = info->num;
56362306a36Sopenharmony_ci		ccw->count = sizeof(info->info_block->l);
56462306a36Sopenharmony_ci	} else {
56562306a36Sopenharmony_ci		info->info_block->s.desc = queue;
56662306a36Sopenharmony_ci		info->info_block->s.index = i;
56762306a36Sopenharmony_ci		info->info_block->s.num = info->num;
56862306a36Sopenharmony_ci		info->info_block->s.avail = (__u64)virtqueue_get_avail_addr(vq);
56962306a36Sopenharmony_ci		info->info_block->s.used = (__u64)virtqueue_get_used_addr(vq);
57062306a36Sopenharmony_ci		ccw->count = sizeof(info->info_block->s);
57162306a36Sopenharmony_ci	}
57262306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_SET_VQ;
57362306a36Sopenharmony_ci	ccw->flags = 0;
57462306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(info->info_block);
57562306a36Sopenharmony_ci	err = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_SET_VQ | i);
57662306a36Sopenharmony_ci	if (err) {
57762306a36Sopenharmony_ci		dev_warn(&vcdev->cdev->dev, "SET_VQ failed\n");
57862306a36Sopenharmony_ci		goto out_err;
57962306a36Sopenharmony_ci	}
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	info->vq = vq;
58262306a36Sopenharmony_ci	vq->priv = info;
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	/* Save it to our list. */
58562306a36Sopenharmony_ci	spin_lock_irqsave(&vcdev->lock, flags);
58662306a36Sopenharmony_ci	list_add(&info->node, &vcdev->virtqueues);
58762306a36Sopenharmony_ci	spin_unlock_irqrestore(&vcdev->lock, flags);
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	return vq;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ciout_err:
59262306a36Sopenharmony_ci	if (vq)
59362306a36Sopenharmony_ci		vring_del_virtqueue(vq);
59462306a36Sopenharmony_ci	if (info) {
59562306a36Sopenharmony_ci		ccw_device_dma_free(vcdev->cdev, info->info_block,
59662306a36Sopenharmony_ci				    sizeof(*info->info_block));
59762306a36Sopenharmony_ci	}
59862306a36Sopenharmony_ci	kfree(info);
59962306a36Sopenharmony_ci	return ERR_PTR(err);
60062306a36Sopenharmony_ci}
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_cistatic int virtio_ccw_register_adapter_ind(struct virtio_ccw_device *vcdev,
60362306a36Sopenharmony_ci					   struct virtqueue *vqs[], int nvqs,
60462306a36Sopenharmony_ci					   struct ccw1 *ccw)
60562306a36Sopenharmony_ci{
60662306a36Sopenharmony_ci	int ret;
60762306a36Sopenharmony_ci	struct virtio_thinint_area *thinint_area = NULL;
60862306a36Sopenharmony_ci	unsigned long indicator_addr;
60962306a36Sopenharmony_ci	struct airq_info *info;
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	thinint_area = ccw_device_dma_zalloc(vcdev->cdev,
61262306a36Sopenharmony_ci					     sizeof(*thinint_area));
61362306a36Sopenharmony_ci	if (!thinint_area) {
61462306a36Sopenharmony_ci		ret = -ENOMEM;
61562306a36Sopenharmony_ci		goto out;
61662306a36Sopenharmony_ci	}
61762306a36Sopenharmony_ci	/* Try to get an indicator. */
61862306a36Sopenharmony_ci	indicator_addr = get_airq_indicator(vqs, nvqs,
61962306a36Sopenharmony_ci					    &thinint_area->bit_nr,
62062306a36Sopenharmony_ci					    &vcdev->airq_info);
62162306a36Sopenharmony_ci	if (!indicator_addr) {
62262306a36Sopenharmony_ci		ret = -ENOSPC;
62362306a36Sopenharmony_ci		goto out;
62462306a36Sopenharmony_ci	}
62562306a36Sopenharmony_ci	thinint_area->indicator = virt_to_phys((void *)indicator_addr);
62662306a36Sopenharmony_ci	info = vcdev->airq_info;
62762306a36Sopenharmony_ci	thinint_area->summary_indicator =
62862306a36Sopenharmony_ci		virt_to_phys(get_summary_indicator(info));
62962306a36Sopenharmony_ci	thinint_area->isc = VIRTIO_AIRQ_ISC;
63062306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_SET_IND_ADAPTER;
63162306a36Sopenharmony_ci	ccw->flags = CCW_FLAG_SLI;
63262306a36Sopenharmony_ci	ccw->count = sizeof(*thinint_area);
63362306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(thinint_area);
63462306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_SET_IND_ADAPTER);
63562306a36Sopenharmony_ci	if (ret) {
63662306a36Sopenharmony_ci		if (ret == -EOPNOTSUPP) {
63762306a36Sopenharmony_ci			/*
63862306a36Sopenharmony_ci			 * The host does not support adapter interrupts
63962306a36Sopenharmony_ci			 * for virtio-ccw, stop trying.
64062306a36Sopenharmony_ci			 */
64162306a36Sopenharmony_ci			virtio_ccw_use_airq = 0;
64262306a36Sopenharmony_ci			pr_info("Adapter interrupts unsupported on host\n");
64362306a36Sopenharmony_ci		} else
64462306a36Sopenharmony_ci			dev_warn(&vcdev->cdev->dev,
64562306a36Sopenharmony_ci				 "enabling adapter interrupts = %d\n", ret);
64662306a36Sopenharmony_ci		virtio_ccw_drop_indicators(vcdev);
64762306a36Sopenharmony_ci	}
64862306a36Sopenharmony_ciout:
64962306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, thinint_area, sizeof(*thinint_area));
65062306a36Sopenharmony_ci	return ret;
65162306a36Sopenharmony_ci}
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_cistatic int virtio_ccw_find_vqs(struct virtio_device *vdev, unsigned nvqs,
65462306a36Sopenharmony_ci			       struct virtqueue *vqs[],
65562306a36Sopenharmony_ci			       vq_callback_t *callbacks[],
65662306a36Sopenharmony_ci			       const char * const names[],
65762306a36Sopenharmony_ci			       const bool *ctx,
65862306a36Sopenharmony_ci			       struct irq_affinity *desc)
65962306a36Sopenharmony_ci{
66062306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
66162306a36Sopenharmony_ci	unsigned long *indicatorp = NULL;
66262306a36Sopenharmony_ci	int ret, i, queue_idx = 0;
66362306a36Sopenharmony_ci	struct ccw1 *ccw;
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
66662306a36Sopenharmony_ci	if (!ccw)
66762306a36Sopenharmony_ci		return -ENOMEM;
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	for (i = 0; i < nvqs; ++i) {
67062306a36Sopenharmony_ci		if (!names[i]) {
67162306a36Sopenharmony_ci			vqs[i] = NULL;
67262306a36Sopenharmony_ci			continue;
67362306a36Sopenharmony_ci		}
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci		vqs[i] = virtio_ccw_setup_vq(vdev, queue_idx++, callbacks[i],
67662306a36Sopenharmony_ci					     names[i], ctx ? ctx[i] : false,
67762306a36Sopenharmony_ci					     ccw);
67862306a36Sopenharmony_ci		if (IS_ERR(vqs[i])) {
67962306a36Sopenharmony_ci			ret = PTR_ERR(vqs[i]);
68062306a36Sopenharmony_ci			vqs[i] = NULL;
68162306a36Sopenharmony_ci			goto out;
68262306a36Sopenharmony_ci		}
68362306a36Sopenharmony_ci	}
68462306a36Sopenharmony_ci	ret = -ENOMEM;
68562306a36Sopenharmony_ci	/*
68662306a36Sopenharmony_ci	 * We need a data area under 2G to communicate. Our payload is
68762306a36Sopenharmony_ci	 * the address of the indicators.
68862306a36Sopenharmony_ci	*/
68962306a36Sopenharmony_ci	indicatorp = ccw_device_dma_zalloc(vcdev->cdev,
69062306a36Sopenharmony_ci					   sizeof(indicators(vcdev)));
69162306a36Sopenharmony_ci	if (!indicatorp)
69262306a36Sopenharmony_ci		goto out;
69362306a36Sopenharmony_ci	*indicatorp = (unsigned long) indicators(vcdev);
69462306a36Sopenharmony_ci	if (vcdev->is_thinint) {
69562306a36Sopenharmony_ci		ret = virtio_ccw_register_adapter_ind(vcdev, vqs, nvqs, ccw);
69662306a36Sopenharmony_ci		if (ret)
69762306a36Sopenharmony_ci			/* no error, just fall back to legacy interrupts */
69862306a36Sopenharmony_ci			vcdev->is_thinint = false;
69962306a36Sopenharmony_ci	}
70062306a36Sopenharmony_ci	if (!vcdev->is_thinint) {
70162306a36Sopenharmony_ci		/* Register queue indicators with host. */
70262306a36Sopenharmony_ci		*indicators(vcdev) = 0;
70362306a36Sopenharmony_ci		ccw->cmd_code = CCW_CMD_SET_IND;
70462306a36Sopenharmony_ci		ccw->flags = 0;
70562306a36Sopenharmony_ci		ccw->count = sizeof(indicators(vcdev));
70662306a36Sopenharmony_ci		ccw->cda = (__u32)virt_to_phys(indicatorp);
70762306a36Sopenharmony_ci		ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_SET_IND);
70862306a36Sopenharmony_ci		if (ret)
70962306a36Sopenharmony_ci			goto out;
71062306a36Sopenharmony_ci	}
71162306a36Sopenharmony_ci	/* Register indicators2 with host for config changes */
71262306a36Sopenharmony_ci	*indicatorp = (unsigned long) indicators2(vcdev);
71362306a36Sopenharmony_ci	*indicators2(vcdev) = 0;
71462306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_SET_CONF_IND;
71562306a36Sopenharmony_ci	ccw->flags = 0;
71662306a36Sopenharmony_ci	ccw->count = sizeof(indicators2(vcdev));
71762306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(indicatorp);
71862306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_SET_CONF_IND);
71962306a36Sopenharmony_ci	if (ret)
72062306a36Sopenharmony_ci		goto out;
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci	if (indicatorp)
72362306a36Sopenharmony_ci		ccw_device_dma_free(vcdev->cdev, indicatorp,
72462306a36Sopenharmony_ci				    sizeof(indicators(vcdev)));
72562306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
72662306a36Sopenharmony_ci	return 0;
72762306a36Sopenharmony_ciout:
72862306a36Sopenharmony_ci	if (indicatorp)
72962306a36Sopenharmony_ci		ccw_device_dma_free(vcdev->cdev, indicatorp,
73062306a36Sopenharmony_ci				    sizeof(indicators(vcdev)));
73162306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
73262306a36Sopenharmony_ci	virtio_ccw_del_vqs(vdev);
73362306a36Sopenharmony_ci	return ret;
73462306a36Sopenharmony_ci}
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_cistatic void virtio_ccw_reset(struct virtio_device *vdev)
73762306a36Sopenharmony_ci{
73862306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
73962306a36Sopenharmony_ci	struct ccw1 *ccw;
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
74262306a36Sopenharmony_ci	if (!ccw)
74362306a36Sopenharmony_ci		return;
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	/* Zero status bits. */
74662306a36Sopenharmony_ci	vcdev->dma_area->status = 0;
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_ci	/* Send a reset ccw on device. */
74962306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_VDEV_RESET;
75062306a36Sopenharmony_ci	ccw->flags = 0;
75162306a36Sopenharmony_ci	ccw->count = 0;
75262306a36Sopenharmony_ci	ccw->cda = 0;
75362306a36Sopenharmony_ci	ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_RESET);
75462306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
75562306a36Sopenharmony_ci}
75662306a36Sopenharmony_ci
75762306a36Sopenharmony_cistatic u64 virtio_ccw_get_features(struct virtio_device *vdev)
75862306a36Sopenharmony_ci{
75962306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
76062306a36Sopenharmony_ci	struct virtio_feature_desc *features;
76162306a36Sopenharmony_ci	int ret;
76262306a36Sopenharmony_ci	u64 rc;
76362306a36Sopenharmony_ci	struct ccw1 *ccw;
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
76662306a36Sopenharmony_ci	if (!ccw)
76762306a36Sopenharmony_ci		return 0;
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	features = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*features));
77062306a36Sopenharmony_ci	if (!features) {
77162306a36Sopenharmony_ci		rc = 0;
77262306a36Sopenharmony_ci		goto out_free;
77362306a36Sopenharmony_ci	}
77462306a36Sopenharmony_ci	/* Read the feature bits from the host. */
77562306a36Sopenharmony_ci	features->index = 0;
77662306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_READ_FEAT;
77762306a36Sopenharmony_ci	ccw->flags = 0;
77862306a36Sopenharmony_ci	ccw->count = sizeof(*features);
77962306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(features);
78062306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_READ_FEAT);
78162306a36Sopenharmony_ci	if (ret) {
78262306a36Sopenharmony_ci		rc = 0;
78362306a36Sopenharmony_ci		goto out_free;
78462306a36Sopenharmony_ci	}
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_ci	rc = le32_to_cpu(features->features);
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	if (vcdev->revision == 0)
78962306a36Sopenharmony_ci		goto out_free;
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci	/* Read second half of the feature bits from the host. */
79262306a36Sopenharmony_ci	features->index = 1;
79362306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_READ_FEAT;
79462306a36Sopenharmony_ci	ccw->flags = 0;
79562306a36Sopenharmony_ci	ccw->count = sizeof(*features);
79662306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(features);
79762306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_READ_FEAT);
79862306a36Sopenharmony_ci	if (ret == 0)
79962306a36Sopenharmony_ci		rc |= (u64)le32_to_cpu(features->features) << 32;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ciout_free:
80262306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, features, sizeof(*features));
80362306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
80462306a36Sopenharmony_ci	return rc;
80562306a36Sopenharmony_ci}
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_cistatic void ccw_transport_features(struct virtio_device *vdev)
80862306a36Sopenharmony_ci{
80962306a36Sopenharmony_ci	/*
81062306a36Sopenharmony_ci	 * Currently nothing to do here.
81162306a36Sopenharmony_ci	 */
81262306a36Sopenharmony_ci}
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_cistatic int virtio_ccw_finalize_features(struct virtio_device *vdev)
81562306a36Sopenharmony_ci{
81662306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
81762306a36Sopenharmony_ci	struct virtio_feature_desc *features;
81862306a36Sopenharmony_ci	struct ccw1 *ccw;
81962306a36Sopenharmony_ci	int ret;
82062306a36Sopenharmony_ci
82162306a36Sopenharmony_ci	if (vcdev->revision >= 1 &&
82262306a36Sopenharmony_ci	    !__virtio_test_bit(vdev, VIRTIO_F_VERSION_1)) {
82362306a36Sopenharmony_ci		dev_err(&vdev->dev, "virtio: device uses revision 1 "
82462306a36Sopenharmony_ci			"but does not have VIRTIO_F_VERSION_1\n");
82562306a36Sopenharmony_ci		return -EINVAL;
82662306a36Sopenharmony_ci	}
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
82962306a36Sopenharmony_ci	if (!ccw)
83062306a36Sopenharmony_ci		return -ENOMEM;
83162306a36Sopenharmony_ci
83262306a36Sopenharmony_ci	features = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*features));
83362306a36Sopenharmony_ci	if (!features) {
83462306a36Sopenharmony_ci		ret = -ENOMEM;
83562306a36Sopenharmony_ci		goto out_free;
83662306a36Sopenharmony_ci	}
83762306a36Sopenharmony_ci	/* Give virtio_ring a chance to accept features. */
83862306a36Sopenharmony_ci	vring_transport_features(vdev);
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	/* Give virtio_ccw a chance to accept features. */
84162306a36Sopenharmony_ci	ccw_transport_features(vdev);
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	features->index = 0;
84462306a36Sopenharmony_ci	features->features = cpu_to_le32((u32)vdev->features);
84562306a36Sopenharmony_ci	/* Write the first half of the feature bits to the host. */
84662306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_WRITE_FEAT;
84762306a36Sopenharmony_ci	ccw->flags = 0;
84862306a36Sopenharmony_ci	ccw->count = sizeof(*features);
84962306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(features);
85062306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_FEAT);
85162306a36Sopenharmony_ci	if (ret)
85262306a36Sopenharmony_ci		goto out_free;
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci	if (vcdev->revision == 0)
85562306a36Sopenharmony_ci		goto out_free;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	features->index = 1;
85862306a36Sopenharmony_ci	features->features = cpu_to_le32(vdev->features >> 32);
85962306a36Sopenharmony_ci	/* Write the second half of the feature bits to the host. */
86062306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_WRITE_FEAT;
86162306a36Sopenharmony_ci	ccw->flags = 0;
86262306a36Sopenharmony_ci	ccw->count = sizeof(*features);
86362306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(features);
86462306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_FEAT);
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ciout_free:
86762306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, features, sizeof(*features));
86862306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci	return ret;
87162306a36Sopenharmony_ci}
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_cistatic void virtio_ccw_get_config(struct virtio_device *vdev,
87462306a36Sopenharmony_ci				  unsigned int offset, void *buf, unsigned len)
87562306a36Sopenharmony_ci{
87662306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
87762306a36Sopenharmony_ci	int ret;
87862306a36Sopenharmony_ci	struct ccw1 *ccw;
87962306a36Sopenharmony_ci	void *config_area;
88062306a36Sopenharmony_ci	unsigned long flags;
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
88362306a36Sopenharmony_ci	if (!ccw)
88462306a36Sopenharmony_ci		return;
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_ci	config_area = ccw_device_dma_zalloc(vcdev->cdev,
88762306a36Sopenharmony_ci					    VIRTIO_CCW_CONFIG_SIZE);
88862306a36Sopenharmony_ci	if (!config_area)
88962306a36Sopenharmony_ci		goto out_free;
89062306a36Sopenharmony_ci
89162306a36Sopenharmony_ci	/* Read the config area from the host. */
89262306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_READ_CONF;
89362306a36Sopenharmony_ci	ccw->flags = 0;
89462306a36Sopenharmony_ci	ccw->count = offset + len;
89562306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(config_area);
89662306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_READ_CONFIG);
89762306a36Sopenharmony_ci	if (ret)
89862306a36Sopenharmony_ci		goto out_free;
89962306a36Sopenharmony_ci
90062306a36Sopenharmony_ci	spin_lock_irqsave(&vcdev->lock, flags);
90162306a36Sopenharmony_ci	memcpy(vcdev->config, config_area, offset + len);
90262306a36Sopenharmony_ci	if (vcdev->config_ready < offset + len)
90362306a36Sopenharmony_ci		vcdev->config_ready = offset + len;
90462306a36Sopenharmony_ci	spin_unlock_irqrestore(&vcdev->lock, flags);
90562306a36Sopenharmony_ci	if (buf)
90662306a36Sopenharmony_ci		memcpy(buf, config_area + offset, len);
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_ciout_free:
90962306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, config_area, VIRTIO_CCW_CONFIG_SIZE);
91062306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
91162306a36Sopenharmony_ci}
91262306a36Sopenharmony_ci
91362306a36Sopenharmony_cistatic void virtio_ccw_set_config(struct virtio_device *vdev,
91462306a36Sopenharmony_ci				  unsigned int offset, const void *buf,
91562306a36Sopenharmony_ci				  unsigned len)
91662306a36Sopenharmony_ci{
91762306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
91862306a36Sopenharmony_ci	struct ccw1 *ccw;
91962306a36Sopenharmony_ci	void *config_area;
92062306a36Sopenharmony_ci	unsigned long flags;
92162306a36Sopenharmony_ci
92262306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
92362306a36Sopenharmony_ci	if (!ccw)
92462306a36Sopenharmony_ci		return;
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	config_area = ccw_device_dma_zalloc(vcdev->cdev,
92762306a36Sopenharmony_ci					    VIRTIO_CCW_CONFIG_SIZE);
92862306a36Sopenharmony_ci	if (!config_area)
92962306a36Sopenharmony_ci		goto out_free;
93062306a36Sopenharmony_ci
93162306a36Sopenharmony_ci	/* Make sure we don't overwrite fields. */
93262306a36Sopenharmony_ci	if (vcdev->config_ready < offset)
93362306a36Sopenharmony_ci		virtio_ccw_get_config(vdev, 0, NULL, offset);
93462306a36Sopenharmony_ci	spin_lock_irqsave(&vcdev->lock, flags);
93562306a36Sopenharmony_ci	memcpy(&vcdev->config[offset], buf, len);
93662306a36Sopenharmony_ci	/* Write the config area to the host. */
93762306a36Sopenharmony_ci	memcpy(config_area, vcdev->config, sizeof(vcdev->config));
93862306a36Sopenharmony_ci	spin_unlock_irqrestore(&vcdev->lock, flags);
93962306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_WRITE_CONF;
94062306a36Sopenharmony_ci	ccw->flags = 0;
94162306a36Sopenharmony_ci	ccw->count = offset + len;
94262306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(config_area);
94362306a36Sopenharmony_ci	ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_CONFIG);
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ciout_free:
94662306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, config_area, VIRTIO_CCW_CONFIG_SIZE);
94762306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
94862306a36Sopenharmony_ci}
94962306a36Sopenharmony_ci
95062306a36Sopenharmony_cistatic u8 virtio_ccw_get_status(struct virtio_device *vdev)
95162306a36Sopenharmony_ci{
95262306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
95362306a36Sopenharmony_ci	u8 old_status = vcdev->dma_area->status;
95462306a36Sopenharmony_ci	struct ccw1 *ccw;
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci	if (vcdev->revision < 2)
95762306a36Sopenharmony_ci		return vcdev->dma_area->status;
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
96062306a36Sopenharmony_ci	if (!ccw)
96162306a36Sopenharmony_ci		return old_status;
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_READ_STATUS;
96462306a36Sopenharmony_ci	ccw->flags = 0;
96562306a36Sopenharmony_ci	ccw->count = sizeof(vcdev->dma_area->status);
96662306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(&vcdev->dma_area->status);
96762306a36Sopenharmony_ci	ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_READ_STATUS);
96862306a36Sopenharmony_ci/*
96962306a36Sopenharmony_ci * If the channel program failed (should only happen if the device
97062306a36Sopenharmony_ci * was hotunplugged, and then we clean up via the machine check
97162306a36Sopenharmony_ci * handler anyway), vcdev->dma_area->status was not overwritten and we just
97262306a36Sopenharmony_ci * return the old status, which is fine.
97362306a36Sopenharmony_ci*/
97462306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
97562306a36Sopenharmony_ci
97662306a36Sopenharmony_ci	return vcdev->dma_area->status;
97762306a36Sopenharmony_ci}
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_cistatic void virtio_ccw_set_status(struct virtio_device *vdev, u8 status)
98062306a36Sopenharmony_ci{
98162306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
98262306a36Sopenharmony_ci	u8 old_status = vcdev->dma_area->status;
98362306a36Sopenharmony_ci	struct ccw1 *ccw;
98462306a36Sopenharmony_ci	int ret;
98562306a36Sopenharmony_ci
98662306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
98762306a36Sopenharmony_ci	if (!ccw)
98862306a36Sopenharmony_ci		return;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	/* Write the status to the host. */
99162306a36Sopenharmony_ci	vcdev->dma_area->status = status;
99262306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_WRITE_STATUS;
99362306a36Sopenharmony_ci	ccw->flags = 0;
99462306a36Sopenharmony_ci	ccw->count = sizeof(status);
99562306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(&vcdev->dma_area->status);
99662306a36Sopenharmony_ci	/* We use ssch for setting the status which is a serializing
99762306a36Sopenharmony_ci	 * instruction that guarantees the memory writes have
99862306a36Sopenharmony_ci	 * completed before ssch.
99962306a36Sopenharmony_ci	 */
100062306a36Sopenharmony_ci	ret = ccw_io_helper(vcdev, ccw, VIRTIO_CCW_DOING_WRITE_STATUS);
100162306a36Sopenharmony_ci	/* Write failed? We assume status is unchanged. */
100262306a36Sopenharmony_ci	if (ret)
100362306a36Sopenharmony_ci		vcdev->dma_area->status = old_status;
100462306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
100562306a36Sopenharmony_ci}
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_cistatic const char *virtio_ccw_bus_name(struct virtio_device *vdev)
100862306a36Sopenharmony_ci{
100962306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
101062306a36Sopenharmony_ci
101162306a36Sopenharmony_ci	return dev_name(&vcdev->cdev->dev);
101262306a36Sopenharmony_ci}
101362306a36Sopenharmony_ci
101462306a36Sopenharmony_cistatic void virtio_ccw_synchronize_cbs(struct virtio_device *vdev)
101562306a36Sopenharmony_ci{
101662306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(vdev);
101762306a36Sopenharmony_ci	struct airq_info *info = vcdev->airq_info;
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_ci	if (info) {
102062306a36Sopenharmony_ci		/*
102162306a36Sopenharmony_ci		 * This device uses adapter interrupts: synchronize with
102262306a36Sopenharmony_ci		 * vring_interrupt() called by virtio_airq_handler()
102362306a36Sopenharmony_ci		 * via the indicator area lock.
102462306a36Sopenharmony_ci		 */
102562306a36Sopenharmony_ci		write_lock_irq(&info->lock);
102662306a36Sopenharmony_ci		write_unlock_irq(&info->lock);
102762306a36Sopenharmony_ci	} else {
102862306a36Sopenharmony_ci		/* This device uses classic interrupts: synchronize
102962306a36Sopenharmony_ci		 * with vring_interrupt() called by
103062306a36Sopenharmony_ci		 * virtio_ccw_int_handler() via the per-device
103162306a36Sopenharmony_ci		 * irq_lock
103262306a36Sopenharmony_ci		 */
103362306a36Sopenharmony_ci		write_lock_irq(&vcdev->irq_lock);
103462306a36Sopenharmony_ci		write_unlock_irq(&vcdev->irq_lock);
103562306a36Sopenharmony_ci	}
103662306a36Sopenharmony_ci}
103762306a36Sopenharmony_ci
103862306a36Sopenharmony_cistatic const struct virtio_config_ops virtio_ccw_config_ops = {
103962306a36Sopenharmony_ci	.get_features = virtio_ccw_get_features,
104062306a36Sopenharmony_ci	.finalize_features = virtio_ccw_finalize_features,
104162306a36Sopenharmony_ci	.get = virtio_ccw_get_config,
104262306a36Sopenharmony_ci	.set = virtio_ccw_set_config,
104362306a36Sopenharmony_ci	.get_status = virtio_ccw_get_status,
104462306a36Sopenharmony_ci	.set_status = virtio_ccw_set_status,
104562306a36Sopenharmony_ci	.reset = virtio_ccw_reset,
104662306a36Sopenharmony_ci	.find_vqs = virtio_ccw_find_vqs,
104762306a36Sopenharmony_ci	.del_vqs = virtio_ccw_del_vqs,
104862306a36Sopenharmony_ci	.bus_name = virtio_ccw_bus_name,
104962306a36Sopenharmony_ci	.synchronize_cbs = virtio_ccw_synchronize_cbs,
105062306a36Sopenharmony_ci};
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_ci
105362306a36Sopenharmony_ci/*
105462306a36Sopenharmony_ci * ccw bus driver related functions
105562306a36Sopenharmony_ci */
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_cistatic void virtio_ccw_release_dev(struct device *_d)
105862306a36Sopenharmony_ci{
105962306a36Sopenharmony_ci	struct virtio_device *dev = dev_to_virtio(_d);
106062306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = to_vc_device(dev);
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, vcdev->dma_area,
106362306a36Sopenharmony_ci			    sizeof(*vcdev->dma_area));
106462306a36Sopenharmony_ci	kfree(vcdev);
106562306a36Sopenharmony_ci}
106662306a36Sopenharmony_ci
106762306a36Sopenharmony_cistatic int irb_is_error(struct irb *irb)
106862306a36Sopenharmony_ci{
106962306a36Sopenharmony_ci	if (scsw_cstat(&irb->scsw) != 0)
107062306a36Sopenharmony_ci		return 1;
107162306a36Sopenharmony_ci	if (scsw_dstat(&irb->scsw) & ~(DEV_STAT_CHN_END | DEV_STAT_DEV_END))
107262306a36Sopenharmony_ci		return 1;
107362306a36Sopenharmony_ci	if (scsw_cc(&irb->scsw) != 0)
107462306a36Sopenharmony_ci		return 1;
107562306a36Sopenharmony_ci	return 0;
107662306a36Sopenharmony_ci}
107762306a36Sopenharmony_ci
107862306a36Sopenharmony_cistatic struct virtqueue *virtio_ccw_vq_by_ind(struct virtio_ccw_device *vcdev,
107962306a36Sopenharmony_ci					      int index)
108062306a36Sopenharmony_ci{
108162306a36Sopenharmony_ci	struct virtio_ccw_vq_info *info;
108262306a36Sopenharmony_ci	unsigned long flags;
108362306a36Sopenharmony_ci	struct virtqueue *vq;
108462306a36Sopenharmony_ci
108562306a36Sopenharmony_ci	vq = NULL;
108662306a36Sopenharmony_ci	spin_lock_irqsave(&vcdev->lock, flags);
108762306a36Sopenharmony_ci	list_for_each_entry(info, &vcdev->virtqueues, node) {
108862306a36Sopenharmony_ci		if (info->vq->index == index) {
108962306a36Sopenharmony_ci			vq = info->vq;
109062306a36Sopenharmony_ci			break;
109162306a36Sopenharmony_ci		}
109262306a36Sopenharmony_ci	}
109362306a36Sopenharmony_ci	spin_unlock_irqrestore(&vcdev->lock, flags);
109462306a36Sopenharmony_ci	return vq;
109562306a36Sopenharmony_ci}
109662306a36Sopenharmony_ci
109762306a36Sopenharmony_cistatic void virtio_ccw_check_activity(struct virtio_ccw_device *vcdev,
109862306a36Sopenharmony_ci				      __u32 activity)
109962306a36Sopenharmony_ci{
110062306a36Sopenharmony_ci	if (vcdev->curr_io & activity) {
110162306a36Sopenharmony_ci		switch (activity) {
110262306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_READ_FEAT:
110362306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_WRITE_FEAT:
110462306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_READ_CONFIG:
110562306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_WRITE_CONFIG:
110662306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_WRITE_STATUS:
110762306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_READ_STATUS:
110862306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_SET_VQ:
110962306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_SET_IND:
111062306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_SET_CONF_IND:
111162306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_RESET:
111262306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_READ_VQ_CONF:
111362306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_SET_IND_ADAPTER:
111462306a36Sopenharmony_ci		case VIRTIO_CCW_DOING_SET_VIRTIO_REV:
111562306a36Sopenharmony_ci			vcdev->curr_io &= ~activity;
111662306a36Sopenharmony_ci			wake_up(&vcdev->wait_q);
111762306a36Sopenharmony_ci			break;
111862306a36Sopenharmony_ci		default:
111962306a36Sopenharmony_ci			/* don't know what to do... */
112062306a36Sopenharmony_ci			dev_warn(&vcdev->cdev->dev,
112162306a36Sopenharmony_ci				 "Suspicious activity '%08x'\n", activity);
112262306a36Sopenharmony_ci			WARN_ON(1);
112362306a36Sopenharmony_ci			break;
112462306a36Sopenharmony_ci		}
112562306a36Sopenharmony_ci	}
112662306a36Sopenharmony_ci}
112762306a36Sopenharmony_ci
112862306a36Sopenharmony_cistatic void virtio_ccw_int_handler(struct ccw_device *cdev,
112962306a36Sopenharmony_ci				   unsigned long intparm,
113062306a36Sopenharmony_ci				   struct irb *irb)
113162306a36Sopenharmony_ci{
113262306a36Sopenharmony_ci	__u32 activity = intparm & VIRTIO_CCW_INTPARM_MASK;
113362306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = dev_get_drvdata(&cdev->dev);
113462306a36Sopenharmony_ci	int i;
113562306a36Sopenharmony_ci	struct virtqueue *vq;
113662306a36Sopenharmony_ci
113762306a36Sopenharmony_ci	if (!vcdev)
113862306a36Sopenharmony_ci		return;
113962306a36Sopenharmony_ci	if (IS_ERR(irb)) {
114062306a36Sopenharmony_ci		vcdev->err = PTR_ERR(irb);
114162306a36Sopenharmony_ci		virtio_ccw_check_activity(vcdev, activity);
114262306a36Sopenharmony_ci		/* Don't poke around indicators, something's wrong. */
114362306a36Sopenharmony_ci		return;
114462306a36Sopenharmony_ci	}
114562306a36Sopenharmony_ci	/* Check if it's a notification from the host. */
114662306a36Sopenharmony_ci	if ((intparm == 0) &&
114762306a36Sopenharmony_ci	    (scsw_stctl(&irb->scsw) ==
114862306a36Sopenharmony_ci	     (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))) {
114962306a36Sopenharmony_ci		/* OK */
115062306a36Sopenharmony_ci	}
115162306a36Sopenharmony_ci	if (irb_is_error(irb)) {
115262306a36Sopenharmony_ci		/* Command reject? */
115362306a36Sopenharmony_ci		if ((scsw_dstat(&irb->scsw) & DEV_STAT_UNIT_CHECK) &&
115462306a36Sopenharmony_ci		    (irb->ecw[0] & SNS0_CMD_REJECT))
115562306a36Sopenharmony_ci			vcdev->err = -EOPNOTSUPP;
115662306a36Sopenharmony_ci		else
115762306a36Sopenharmony_ci			/* Map everything else to -EIO. */
115862306a36Sopenharmony_ci			vcdev->err = -EIO;
115962306a36Sopenharmony_ci	}
116062306a36Sopenharmony_ci	virtio_ccw_check_activity(vcdev, activity);
116162306a36Sopenharmony_ci#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
116262306a36Sopenharmony_ci	/*
116362306a36Sopenharmony_ci	 * Paired with virtio_ccw_synchronize_cbs() and interrupts are
116462306a36Sopenharmony_ci	 * disabled here.
116562306a36Sopenharmony_ci	 */
116662306a36Sopenharmony_ci	read_lock(&vcdev->irq_lock);
116762306a36Sopenharmony_ci#endif
116862306a36Sopenharmony_ci	for_each_set_bit(i, indicators(vcdev),
116962306a36Sopenharmony_ci			 sizeof(*indicators(vcdev)) * BITS_PER_BYTE) {
117062306a36Sopenharmony_ci		/* The bit clear must happen before the vring kick. */
117162306a36Sopenharmony_ci		clear_bit(i, indicators(vcdev));
117262306a36Sopenharmony_ci		barrier();
117362306a36Sopenharmony_ci		vq = virtio_ccw_vq_by_ind(vcdev, i);
117462306a36Sopenharmony_ci		vring_interrupt(0, vq);
117562306a36Sopenharmony_ci	}
117662306a36Sopenharmony_ci#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
117762306a36Sopenharmony_ci	read_unlock(&vcdev->irq_lock);
117862306a36Sopenharmony_ci#endif
117962306a36Sopenharmony_ci	if (test_bit(0, indicators2(vcdev))) {
118062306a36Sopenharmony_ci		virtio_config_changed(&vcdev->vdev);
118162306a36Sopenharmony_ci		clear_bit(0, indicators2(vcdev));
118262306a36Sopenharmony_ci	}
118362306a36Sopenharmony_ci}
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_ci/*
118662306a36Sopenharmony_ci * We usually want to autoonline all devices, but give the admin
118762306a36Sopenharmony_ci * a way to exempt devices from this.
118862306a36Sopenharmony_ci */
118962306a36Sopenharmony_ci#define __DEV_WORDS ((__MAX_SUBCHANNEL + (8*sizeof(long) - 1)) / \
119062306a36Sopenharmony_ci		     (8*sizeof(long)))
119162306a36Sopenharmony_cistatic unsigned long devs_no_auto[__MAX_SSID + 1][__DEV_WORDS];
119262306a36Sopenharmony_ci
119362306a36Sopenharmony_cistatic char *no_auto = "";
119462306a36Sopenharmony_ci
119562306a36Sopenharmony_cimodule_param(no_auto, charp, 0444);
119662306a36Sopenharmony_ciMODULE_PARM_DESC(no_auto, "list of ccw bus id ranges not to be auto-onlined");
119762306a36Sopenharmony_ci
119862306a36Sopenharmony_cistatic int virtio_ccw_check_autoonline(struct ccw_device *cdev)
119962306a36Sopenharmony_ci{
120062306a36Sopenharmony_ci	struct ccw_dev_id id;
120162306a36Sopenharmony_ci
120262306a36Sopenharmony_ci	ccw_device_get_id(cdev, &id);
120362306a36Sopenharmony_ci	if (test_bit(id.devno, devs_no_auto[id.ssid]))
120462306a36Sopenharmony_ci		return 0;
120562306a36Sopenharmony_ci	return 1;
120662306a36Sopenharmony_ci}
120762306a36Sopenharmony_ci
120862306a36Sopenharmony_cistatic void virtio_ccw_auto_online(void *data, async_cookie_t cookie)
120962306a36Sopenharmony_ci{
121062306a36Sopenharmony_ci	struct ccw_device *cdev = data;
121162306a36Sopenharmony_ci	int ret;
121262306a36Sopenharmony_ci
121362306a36Sopenharmony_ci	ret = ccw_device_set_online(cdev);
121462306a36Sopenharmony_ci	if (ret)
121562306a36Sopenharmony_ci		dev_warn(&cdev->dev, "Failed to set online: %d\n", ret);
121662306a36Sopenharmony_ci}
121762306a36Sopenharmony_ci
121862306a36Sopenharmony_cistatic int virtio_ccw_probe(struct ccw_device *cdev)
121962306a36Sopenharmony_ci{
122062306a36Sopenharmony_ci	cdev->handler = virtio_ccw_int_handler;
122162306a36Sopenharmony_ci
122262306a36Sopenharmony_ci	if (virtio_ccw_check_autoonline(cdev))
122362306a36Sopenharmony_ci		async_schedule(virtio_ccw_auto_online, cdev);
122462306a36Sopenharmony_ci	return 0;
122562306a36Sopenharmony_ci}
122662306a36Sopenharmony_ci
122762306a36Sopenharmony_cistatic struct virtio_ccw_device *virtio_grab_drvdata(struct ccw_device *cdev)
122862306a36Sopenharmony_ci{
122962306a36Sopenharmony_ci	unsigned long flags;
123062306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev;
123162306a36Sopenharmony_ci
123262306a36Sopenharmony_ci	spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
123362306a36Sopenharmony_ci	vcdev = dev_get_drvdata(&cdev->dev);
123462306a36Sopenharmony_ci	if (!vcdev || vcdev->going_away) {
123562306a36Sopenharmony_ci		spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
123662306a36Sopenharmony_ci		return NULL;
123762306a36Sopenharmony_ci	}
123862306a36Sopenharmony_ci	vcdev->going_away = true;
123962306a36Sopenharmony_ci	spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
124062306a36Sopenharmony_ci	return vcdev;
124162306a36Sopenharmony_ci}
124262306a36Sopenharmony_ci
124362306a36Sopenharmony_cistatic void virtio_ccw_remove(struct ccw_device *cdev)
124462306a36Sopenharmony_ci{
124562306a36Sopenharmony_ci	unsigned long flags;
124662306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev);
124762306a36Sopenharmony_ci
124862306a36Sopenharmony_ci	if (vcdev && cdev->online) {
124962306a36Sopenharmony_ci		if (vcdev->device_lost)
125062306a36Sopenharmony_ci			virtio_break_device(&vcdev->vdev);
125162306a36Sopenharmony_ci		unregister_virtio_device(&vcdev->vdev);
125262306a36Sopenharmony_ci		spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
125362306a36Sopenharmony_ci		dev_set_drvdata(&cdev->dev, NULL);
125462306a36Sopenharmony_ci		spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
125562306a36Sopenharmony_ci	}
125662306a36Sopenharmony_ci	cdev->handler = NULL;
125762306a36Sopenharmony_ci}
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_cistatic int virtio_ccw_offline(struct ccw_device *cdev)
126062306a36Sopenharmony_ci{
126162306a36Sopenharmony_ci	unsigned long flags;
126262306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = virtio_grab_drvdata(cdev);
126362306a36Sopenharmony_ci
126462306a36Sopenharmony_ci	if (!vcdev)
126562306a36Sopenharmony_ci		return 0;
126662306a36Sopenharmony_ci	if (vcdev->device_lost)
126762306a36Sopenharmony_ci		virtio_break_device(&vcdev->vdev);
126862306a36Sopenharmony_ci	unregister_virtio_device(&vcdev->vdev);
126962306a36Sopenharmony_ci	spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
127062306a36Sopenharmony_ci	dev_set_drvdata(&cdev->dev, NULL);
127162306a36Sopenharmony_ci	spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
127262306a36Sopenharmony_ci	return 0;
127362306a36Sopenharmony_ci}
127462306a36Sopenharmony_ci
127562306a36Sopenharmony_cistatic int virtio_ccw_set_transport_rev(struct virtio_ccw_device *vcdev)
127662306a36Sopenharmony_ci{
127762306a36Sopenharmony_ci	struct virtio_rev_info *rev;
127862306a36Sopenharmony_ci	struct ccw1 *ccw;
127962306a36Sopenharmony_ci	int ret;
128062306a36Sopenharmony_ci
128162306a36Sopenharmony_ci	ccw = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*ccw));
128262306a36Sopenharmony_ci	if (!ccw)
128362306a36Sopenharmony_ci		return -ENOMEM;
128462306a36Sopenharmony_ci	rev = ccw_device_dma_zalloc(vcdev->cdev, sizeof(*rev));
128562306a36Sopenharmony_ci	if (!rev) {
128662306a36Sopenharmony_ci		ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
128762306a36Sopenharmony_ci		return -ENOMEM;
128862306a36Sopenharmony_ci	}
128962306a36Sopenharmony_ci
129062306a36Sopenharmony_ci	/* Set transport revision */
129162306a36Sopenharmony_ci	ccw->cmd_code = CCW_CMD_SET_VIRTIO_REV;
129262306a36Sopenharmony_ci	ccw->flags = 0;
129362306a36Sopenharmony_ci	ccw->count = sizeof(*rev);
129462306a36Sopenharmony_ci	ccw->cda = (__u32)virt_to_phys(rev);
129562306a36Sopenharmony_ci
129662306a36Sopenharmony_ci	vcdev->revision = VIRTIO_CCW_REV_MAX;
129762306a36Sopenharmony_ci	do {
129862306a36Sopenharmony_ci		rev->revision = vcdev->revision;
129962306a36Sopenharmony_ci		/* none of our supported revisions carry payload */
130062306a36Sopenharmony_ci		rev->length = 0;
130162306a36Sopenharmony_ci		ret = ccw_io_helper(vcdev, ccw,
130262306a36Sopenharmony_ci				    VIRTIO_CCW_DOING_SET_VIRTIO_REV);
130362306a36Sopenharmony_ci		if (ret == -EOPNOTSUPP) {
130462306a36Sopenharmony_ci			if (vcdev->revision == 0)
130562306a36Sopenharmony_ci				/*
130662306a36Sopenharmony_ci				 * The host device does not support setting
130762306a36Sopenharmony_ci				 * the revision: let's operate it in legacy
130862306a36Sopenharmony_ci				 * mode.
130962306a36Sopenharmony_ci				 */
131062306a36Sopenharmony_ci				ret = 0;
131162306a36Sopenharmony_ci			else
131262306a36Sopenharmony_ci				vcdev->revision--;
131362306a36Sopenharmony_ci		}
131462306a36Sopenharmony_ci	} while (ret == -EOPNOTSUPP);
131562306a36Sopenharmony_ci
131662306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, ccw, sizeof(*ccw));
131762306a36Sopenharmony_ci	ccw_device_dma_free(vcdev->cdev, rev, sizeof(*rev));
131862306a36Sopenharmony_ci	return ret;
131962306a36Sopenharmony_ci}
132062306a36Sopenharmony_ci
132162306a36Sopenharmony_cistatic int virtio_ccw_online(struct ccw_device *cdev)
132262306a36Sopenharmony_ci{
132362306a36Sopenharmony_ci	int ret;
132462306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev;
132562306a36Sopenharmony_ci	unsigned long flags;
132662306a36Sopenharmony_ci
132762306a36Sopenharmony_ci	vcdev = kzalloc(sizeof(*vcdev), GFP_KERNEL);
132862306a36Sopenharmony_ci	if (!vcdev) {
132962306a36Sopenharmony_ci		dev_warn(&cdev->dev, "Could not get memory for virtio\n");
133062306a36Sopenharmony_ci		ret = -ENOMEM;
133162306a36Sopenharmony_ci		goto out_free;
133262306a36Sopenharmony_ci	}
133362306a36Sopenharmony_ci	vcdev->vdev.dev.parent = &cdev->dev;
133462306a36Sopenharmony_ci	vcdev->cdev = cdev;
133562306a36Sopenharmony_ci	vcdev->dma_area = ccw_device_dma_zalloc(vcdev->cdev,
133662306a36Sopenharmony_ci						sizeof(*vcdev->dma_area));
133762306a36Sopenharmony_ci	if (!vcdev->dma_area) {
133862306a36Sopenharmony_ci		ret = -ENOMEM;
133962306a36Sopenharmony_ci		goto out_free;
134062306a36Sopenharmony_ci	}
134162306a36Sopenharmony_ci
134262306a36Sopenharmony_ci	vcdev->is_thinint = virtio_ccw_use_airq; /* at least try */
134362306a36Sopenharmony_ci
134462306a36Sopenharmony_ci	vcdev->vdev.dev.release = virtio_ccw_release_dev;
134562306a36Sopenharmony_ci	vcdev->vdev.config = &virtio_ccw_config_ops;
134662306a36Sopenharmony_ci	init_waitqueue_head(&vcdev->wait_q);
134762306a36Sopenharmony_ci	INIT_LIST_HEAD(&vcdev->virtqueues);
134862306a36Sopenharmony_ci	spin_lock_init(&vcdev->lock);
134962306a36Sopenharmony_ci	rwlock_init(&vcdev->irq_lock);
135062306a36Sopenharmony_ci	mutex_init(&vcdev->io_lock);
135162306a36Sopenharmony_ci
135262306a36Sopenharmony_ci	spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
135362306a36Sopenharmony_ci	dev_set_drvdata(&cdev->dev, vcdev);
135462306a36Sopenharmony_ci	spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
135562306a36Sopenharmony_ci	vcdev->vdev.id.vendor = cdev->id.cu_type;
135662306a36Sopenharmony_ci	vcdev->vdev.id.device = cdev->id.cu_model;
135762306a36Sopenharmony_ci
135862306a36Sopenharmony_ci	ret = virtio_ccw_set_transport_rev(vcdev);
135962306a36Sopenharmony_ci	if (ret)
136062306a36Sopenharmony_ci		goto out_free;
136162306a36Sopenharmony_ci
136262306a36Sopenharmony_ci	ret = register_virtio_device(&vcdev->vdev);
136362306a36Sopenharmony_ci	if (ret) {
136462306a36Sopenharmony_ci		dev_warn(&cdev->dev, "Failed to register virtio device: %d\n",
136562306a36Sopenharmony_ci			 ret);
136662306a36Sopenharmony_ci		goto out_put;
136762306a36Sopenharmony_ci	}
136862306a36Sopenharmony_ci	return 0;
136962306a36Sopenharmony_ciout_put:
137062306a36Sopenharmony_ci	spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
137162306a36Sopenharmony_ci	dev_set_drvdata(&cdev->dev, NULL);
137262306a36Sopenharmony_ci	spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
137362306a36Sopenharmony_ci	put_device(&vcdev->vdev.dev);
137462306a36Sopenharmony_ci	return ret;
137562306a36Sopenharmony_ciout_free:
137662306a36Sopenharmony_ci	if (vcdev) {
137762306a36Sopenharmony_ci		ccw_device_dma_free(vcdev->cdev, vcdev->dma_area,
137862306a36Sopenharmony_ci				    sizeof(*vcdev->dma_area));
137962306a36Sopenharmony_ci	}
138062306a36Sopenharmony_ci	kfree(vcdev);
138162306a36Sopenharmony_ci	return ret;
138262306a36Sopenharmony_ci}
138362306a36Sopenharmony_ci
138462306a36Sopenharmony_cistatic int virtio_ccw_cio_notify(struct ccw_device *cdev, int event)
138562306a36Sopenharmony_ci{
138662306a36Sopenharmony_ci	int rc;
138762306a36Sopenharmony_ci	struct virtio_ccw_device *vcdev = dev_get_drvdata(&cdev->dev);
138862306a36Sopenharmony_ci
138962306a36Sopenharmony_ci	/*
139062306a36Sopenharmony_ci	 * Make sure vcdev is set
139162306a36Sopenharmony_ci	 * i.e. set_offline/remove callback not already running
139262306a36Sopenharmony_ci	 */
139362306a36Sopenharmony_ci	if (!vcdev)
139462306a36Sopenharmony_ci		return NOTIFY_DONE;
139562306a36Sopenharmony_ci
139662306a36Sopenharmony_ci	switch (event) {
139762306a36Sopenharmony_ci	case CIO_GONE:
139862306a36Sopenharmony_ci		vcdev->device_lost = true;
139962306a36Sopenharmony_ci		rc = NOTIFY_DONE;
140062306a36Sopenharmony_ci		break;
140162306a36Sopenharmony_ci	case CIO_OPER:
140262306a36Sopenharmony_ci		rc = NOTIFY_OK;
140362306a36Sopenharmony_ci		break;
140462306a36Sopenharmony_ci	default:
140562306a36Sopenharmony_ci		rc = NOTIFY_DONE;
140662306a36Sopenharmony_ci		break;
140762306a36Sopenharmony_ci	}
140862306a36Sopenharmony_ci	return rc;
140962306a36Sopenharmony_ci}
141062306a36Sopenharmony_ci
141162306a36Sopenharmony_cistatic struct ccw_device_id virtio_ids[] = {
141262306a36Sopenharmony_ci	{ CCW_DEVICE(0x3832, 0) },
141362306a36Sopenharmony_ci	{},
141462306a36Sopenharmony_ci};
141562306a36Sopenharmony_ci
141662306a36Sopenharmony_cistatic struct ccw_driver virtio_ccw_driver = {
141762306a36Sopenharmony_ci	.driver = {
141862306a36Sopenharmony_ci		.owner = THIS_MODULE,
141962306a36Sopenharmony_ci		.name = "virtio_ccw",
142062306a36Sopenharmony_ci	},
142162306a36Sopenharmony_ci	.ids = virtio_ids,
142262306a36Sopenharmony_ci	.probe = virtio_ccw_probe,
142362306a36Sopenharmony_ci	.remove = virtio_ccw_remove,
142462306a36Sopenharmony_ci	.set_offline = virtio_ccw_offline,
142562306a36Sopenharmony_ci	.set_online = virtio_ccw_online,
142662306a36Sopenharmony_ci	.notify = virtio_ccw_cio_notify,
142762306a36Sopenharmony_ci	.int_class = IRQIO_VIR,
142862306a36Sopenharmony_ci};
142962306a36Sopenharmony_ci
143062306a36Sopenharmony_cistatic int __init pure_hex(char **cp, unsigned int *val, int min_digit,
143162306a36Sopenharmony_ci			   int max_digit, int max_val)
143262306a36Sopenharmony_ci{
143362306a36Sopenharmony_ci	int diff;
143462306a36Sopenharmony_ci
143562306a36Sopenharmony_ci	diff = 0;
143662306a36Sopenharmony_ci	*val = 0;
143762306a36Sopenharmony_ci
143862306a36Sopenharmony_ci	while (diff <= max_digit) {
143962306a36Sopenharmony_ci		int value = hex_to_bin(**cp);
144062306a36Sopenharmony_ci
144162306a36Sopenharmony_ci		if (value < 0)
144262306a36Sopenharmony_ci			break;
144362306a36Sopenharmony_ci		*val = *val * 16 + value;
144462306a36Sopenharmony_ci		(*cp)++;
144562306a36Sopenharmony_ci		diff++;
144662306a36Sopenharmony_ci	}
144762306a36Sopenharmony_ci
144862306a36Sopenharmony_ci	if ((diff < min_digit) || (diff > max_digit) || (*val > max_val))
144962306a36Sopenharmony_ci		return 1;
145062306a36Sopenharmony_ci
145162306a36Sopenharmony_ci	return 0;
145262306a36Sopenharmony_ci}
145362306a36Sopenharmony_ci
145462306a36Sopenharmony_cistatic int __init parse_busid(char *str, unsigned int *cssid,
145562306a36Sopenharmony_ci			      unsigned int *ssid, unsigned int *devno)
145662306a36Sopenharmony_ci{
145762306a36Sopenharmony_ci	char *str_work;
145862306a36Sopenharmony_ci	int rc, ret;
145962306a36Sopenharmony_ci
146062306a36Sopenharmony_ci	rc = 1;
146162306a36Sopenharmony_ci
146262306a36Sopenharmony_ci	if (*str == '\0')
146362306a36Sopenharmony_ci		goto out;
146462306a36Sopenharmony_ci
146562306a36Sopenharmony_ci	str_work = str;
146662306a36Sopenharmony_ci	ret = pure_hex(&str_work, cssid, 1, 2, __MAX_CSSID);
146762306a36Sopenharmony_ci	if (ret || (str_work[0] != '.'))
146862306a36Sopenharmony_ci		goto out;
146962306a36Sopenharmony_ci	str_work++;
147062306a36Sopenharmony_ci	ret = pure_hex(&str_work, ssid, 1, 1, __MAX_SSID);
147162306a36Sopenharmony_ci	if (ret || (str_work[0] != '.'))
147262306a36Sopenharmony_ci		goto out;
147362306a36Sopenharmony_ci	str_work++;
147462306a36Sopenharmony_ci	ret = pure_hex(&str_work, devno, 4, 4, __MAX_SUBCHANNEL);
147562306a36Sopenharmony_ci	if (ret || (str_work[0] != '\0'))
147662306a36Sopenharmony_ci		goto out;
147762306a36Sopenharmony_ci
147862306a36Sopenharmony_ci	rc = 0;
147962306a36Sopenharmony_ciout:
148062306a36Sopenharmony_ci	return rc;
148162306a36Sopenharmony_ci}
148262306a36Sopenharmony_ci
148362306a36Sopenharmony_cistatic void __init no_auto_parse(void)
148462306a36Sopenharmony_ci{
148562306a36Sopenharmony_ci	unsigned int from_cssid, to_cssid, from_ssid, to_ssid, from, to;
148662306a36Sopenharmony_ci	char *parm, *str;
148762306a36Sopenharmony_ci	int rc;
148862306a36Sopenharmony_ci
148962306a36Sopenharmony_ci	str = no_auto;
149062306a36Sopenharmony_ci	while ((parm = strsep(&str, ","))) {
149162306a36Sopenharmony_ci		rc = parse_busid(strsep(&parm, "-"), &from_cssid,
149262306a36Sopenharmony_ci				 &from_ssid, &from);
149362306a36Sopenharmony_ci		if (rc)
149462306a36Sopenharmony_ci			continue;
149562306a36Sopenharmony_ci		if (parm != NULL) {
149662306a36Sopenharmony_ci			rc = parse_busid(parm, &to_cssid,
149762306a36Sopenharmony_ci					 &to_ssid, &to);
149862306a36Sopenharmony_ci			if ((from_ssid > to_ssid) ||
149962306a36Sopenharmony_ci			    ((from_ssid == to_ssid) && (from > to)))
150062306a36Sopenharmony_ci				rc = -EINVAL;
150162306a36Sopenharmony_ci		} else {
150262306a36Sopenharmony_ci			to_cssid = from_cssid;
150362306a36Sopenharmony_ci			to_ssid = from_ssid;
150462306a36Sopenharmony_ci			to = from;
150562306a36Sopenharmony_ci		}
150662306a36Sopenharmony_ci		if (rc)
150762306a36Sopenharmony_ci			continue;
150862306a36Sopenharmony_ci		while ((from_ssid < to_ssid) ||
150962306a36Sopenharmony_ci		       ((from_ssid == to_ssid) && (from <= to))) {
151062306a36Sopenharmony_ci			set_bit(from, devs_no_auto[from_ssid]);
151162306a36Sopenharmony_ci			from++;
151262306a36Sopenharmony_ci			if (from > __MAX_SUBCHANNEL) {
151362306a36Sopenharmony_ci				from_ssid++;
151462306a36Sopenharmony_ci				from = 0;
151562306a36Sopenharmony_ci			}
151662306a36Sopenharmony_ci		}
151762306a36Sopenharmony_ci	}
151862306a36Sopenharmony_ci}
151962306a36Sopenharmony_ci
152062306a36Sopenharmony_cistatic int __init virtio_ccw_init(void)
152162306a36Sopenharmony_ci{
152262306a36Sopenharmony_ci	int rc;
152362306a36Sopenharmony_ci
152462306a36Sopenharmony_ci	/* parse no_auto string before we do anything further */
152562306a36Sopenharmony_ci	no_auto_parse();
152662306a36Sopenharmony_ci
152762306a36Sopenharmony_ci	summary_indicators = cio_dma_zalloc(MAX_AIRQ_AREAS);
152862306a36Sopenharmony_ci	if (!summary_indicators)
152962306a36Sopenharmony_ci		return -ENOMEM;
153062306a36Sopenharmony_ci	rc = ccw_driver_register(&virtio_ccw_driver);
153162306a36Sopenharmony_ci	if (rc)
153262306a36Sopenharmony_ci		cio_dma_free(summary_indicators, MAX_AIRQ_AREAS);
153362306a36Sopenharmony_ci	return rc;
153462306a36Sopenharmony_ci}
153562306a36Sopenharmony_cidevice_initcall(virtio_ccw_init);
1536