18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  CCW device SENSE ID I/O handling.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *    Copyright IBM Corp. 2002, 2009
68c2ecf20Sopenharmony_ci *    Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
78c2ecf20Sopenharmony_ci *		 Martin Schwidefsky <schwidefsky@de.ibm.com>
88c2ecf20Sopenharmony_ci *		 Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/string.h>
138c2ecf20Sopenharmony_ci#include <linux/types.h>
148c2ecf20Sopenharmony_ci#include <linux/errno.h>
158c2ecf20Sopenharmony_ci#include <asm/ccwdev.h>
168c2ecf20Sopenharmony_ci#include <asm/setup.h>
178c2ecf20Sopenharmony_ci#include <asm/cio.h>
188c2ecf20Sopenharmony_ci#include <asm/diag.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include "cio.h"
218c2ecf20Sopenharmony_ci#include "cio_debug.h"
228c2ecf20Sopenharmony_ci#include "device.h"
238c2ecf20Sopenharmony_ci#include "io_sch.h"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define SENSE_ID_RETRIES	256
268c2ecf20Sopenharmony_ci#define SENSE_ID_TIMEOUT	(10 * HZ)
278c2ecf20Sopenharmony_ci#define SENSE_ID_MIN_LEN	4
288c2ecf20Sopenharmony_ci#define SENSE_ID_BASIC_LEN	7
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci/**
318c2ecf20Sopenharmony_ci * diag210_to_senseid - convert diag 0x210 data to sense id information
328c2ecf20Sopenharmony_ci * @senseid: sense id
338c2ecf20Sopenharmony_ci * @diag: diag 0x210 data
348c2ecf20Sopenharmony_ci *
358c2ecf20Sopenharmony_ci * Return 0 on success, non-zero otherwise.
368c2ecf20Sopenharmony_ci */
378c2ecf20Sopenharmony_cistatic int diag210_to_senseid(struct senseid *senseid, struct diag210 *diag)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	static struct {
408c2ecf20Sopenharmony_ci		int class, type, cu_type;
418c2ecf20Sopenharmony_ci	} vm_devices[] = {
428c2ecf20Sopenharmony_ci		{ 0x08, 0x01, 0x3480 },
438c2ecf20Sopenharmony_ci		{ 0x08, 0x02, 0x3430 },
448c2ecf20Sopenharmony_ci		{ 0x08, 0x10, 0x3420 },
458c2ecf20Sopenharmony_ci		{ 0x08, 0x42, 0x3424 },
468c2ecf20Sopenharmony_ci		{ 0x08, 0x44, 0x9348 },
478c2ecf20Sopenharmony_ci		{ 0x08, 0x81, 0x3490 },
488c2ecf20Sopenharmony_ci		{ 0x08, 0x82, 0x3422 },
498c2ecf20Sopenharmony_ci		{ 0x10, 0x41, 0x1403 },
508c2ecf20Sopenharmony_ci		{ 0x10, 0x42, 0x3211 },
518c2ecf20Sopenharmony_ci		{ 0x10, 0x43, 0x3203 },
528c2ecf20Sopenharmony_ci		{ 0x10, 0x45, 0x3800 },
538c2ecf20Sopenharmony_ci		{ 0x10, 0x47, 0x3262 },
548c2ecf20Sopenharmony_ci		{ 0x10, 0x48, 0x3820 },
558c2ecf20Sopenharmony_ci		{ 0x10, 0x49, 0x3800 },
568c2ecf20Sopenharmony_ci		{ 0x10, 0x4a, 0x4245 },
578c2ecf20Sopenharmony_ci		{ 0x10, 0x4b, 0x4248 },
588c2ecf20Sopenharmony_ci		{ 0x10, 0x4d, 0x3800 },
598c2ecf20Sopenharmony_ci		{ 0x10, 0x4e, 0x3820 },
608c2ecf20Sopenharmony_ci		{ 0x10, 0x4f, 0x3820 },
618c2ecf20Sopenharmony_ci		{ 0x10, 0x82, 0x2540 },
628c2ecf20Sopenharmony_ci		{ 0x10, 0x84, 0x3525 },
638c2ecf20Sopenharmony_ci		{ 0x20, 0x81, 0x2501 },
648c2ecf20Sopenharmony_ci		{ 0x20, 0x82, 0x2540 },
658c2ecf20Sopenharmony_ci		{ 0x20, 0x84, 0x3505 },
668c2ecf20Sopenharmony_ci		{ 0x40, 0x01, 0x3278 },
678c2ecf20Sopenharmony_ci		{ 0x40, 0x04, 0x3277 },
688c2ecf20Sopenharmony_ci		{ 0x40, 0x80, 0x2250 },
698c2ecf20Sopenharmony_ci		{ 0x40, 0xc0, 0x5080 },
708c2ecf20Sopenharmony_ci		{ 0x80, 0x00, 0x3215 },
718c2ecf20Sopenharmony_ci	};
728c2ecf20Sopenharmony_ci	int i;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	/* Special case for osa devices. */
758c2ecf20Sopenharmony_ci	if (diag->vrdcvcla == 0x02 && diag->vrdcvtyp == 0x20) {
768c2ecf20Sopenharmony_ci		senseid->cu_type = 0x3088;
778c2ecf20Sopenharmony_ci		senseid->cu_model = 0x60;
788c2ecf20Sopenharmony_ci		senseid->reserved = 0xff;
798c2ecf20Sopenharmony_ci		return 0;
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(vm_devices); i++) {
828c2ecf20Sopenharmony_ci		if (diag->vrdcvcla == vm_devices[i].class &&
838c2ecf20Sopenharmony_ci		    diag->vrdcvtyp == vm_devices[i].type) {
848c2ecf20Sopenharmony_ci			senseid->cu_type = vm_devices[i].cu_type;
858c2ecf20Sopenharmony_ci			senseid->reserved = 0xff;
868c2ecf20Sopenharmony_ci			return 0;
878c2ecf20Sopenharmony_ci		}
888c2ecf20Sopenharmony_ci	}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	return -ENODEV;
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci/**
948c2ecf20Sopenharmony_ci * diag_get_dev_info - retrieve device information via diag 0x210
958c2ecf20Sopenharmony_ci * @cdev: ccw device
968c2ecf20Sopenharmony_ci *
978c2ecf20Sopenharmony_ci * Returns zero on success, non-zero otherwise.
988c2ecf20Sopenharmony_ci */
998c2ecf20Sopenharmony_cistatic int diag210_get_dev_info(struct ccw_device *cdev)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	struct ccw_dev_id *dev_id = &cdev->private->dev_id;
1028c2ecf20Sopenharmony_ci	struct senseid *senseid = &cdev->private->dma_area->senseid;
1038c2ecf20Sopenharmony_ci	struct diag210 diag_data;
1048c2ecf20Sopenharmony_ci	int rc;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	if (dev_id->ssid != 0)
1078c2ecf20Sopenharmony_ci		return -ENODEV;
1088c2ecf20Sopenharmony_ci	memset(&diag_data, 0, sizeof(diag_data));
1098c2ecf20Sopenharmony_ci	diag_data.vrdcdvno	= dev_id->devno;
1108c2ecf20Sopenharmony_ci	diag_data.vrdclen	= sizeof(diag_data);
1118c2ecf20Sopenharmony_ci	rc = diag210(&diag_data);
1128c2ecf20Sopenharmony_ci	CIO_TRACE_EVENT(4, "diag210");
1138c2ecf20Sopenharmony_ci	CIO_HEX_EVENT(4, &rc, sizeof(rc));
1148c2ecf20Sopenharmony_ci	CIO_HEX_EVENT(4, &diag_data, sizeof(diag_data));
1158c2ecf20Sopenharmony_ci	if (rc != 0 && rc != 2)
1168c2ecf20Sopenharmony_ci		goto err_failed;
1178c2ecf20Sopenharmony_ci	if (diag210_to_senseid(senseid, &diag_data))
1188c2ecf20Sopenharmony_ci		goto err_unknown;
1198c2ecf20Sopenharmony_ci	return 0;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cierr_unknown:
1228c2ecf20Sopenharmony_ci	CIO_MSG_EVENT(0, "snsid: device 0.%x.%04x: unknown diag210 data\n",
1238c2ecf20Sopenharmony_ci		      dev_id->ssid, dev_id->devno);
1248c2ecf20Sopenharmony_ci	return -ENODEV;
1258c2ecf20Sopenharmony_cierr_failed:
1268c2ecf20Sopenharmony_ci	CIO_MSG_EVENT(0, "snsid: device 0.%x.%04x: diag210 failed (rc=%d)\n",
1278c2ecf20Sopenharmony_ci		      dev_id->ssid, dev_id->devno, rc);
1288c2ecf20Sopenharmony_ci	return -ENODEV;
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci/*
1328c2ecf20Sopenharmony_ci * Initialize SENSE ID data.
1338c2ecf20Sopenharmony_ci */
1348c2ecf20Sopenharmony_cistatic void snsid_init(struct ccw_device *cdev)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	cdev->private->flags.esid = 0;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	memset(&cdev->private->dma_area->senseid, 0,
1398c2ecf20Sopenharmony_ci	       sizeof(cdev->private->dma_area->senseid));
1408c2ecf20Sopenharmony_ci	cdev->private->dma_area->senseid.cu_type = 0xffff;
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci/*
1448c2ecf20Sopenharmony_ci * Check for complete SENSE ID data.
1458c2ecf20Sopenharmony_ci */
1468c2ecf20Sopenharmony_cistatic int snsid_check(struct ccw_device *cdev, void *data)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	struct cmd_scsw *scsw = &cdev->private->dma_area->irb.scsw.cmd;
1498c2ecf20Sopenharmony_ci	int len = sizeof(struct senseid) - scsw->count;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	/* Check for incomplete SENSE ID data. */
1528c2ecf20Sopenharmony_ci	if (len < SENSE_ID_MIN_LEN)
1538c2ecf20Sopenharmony_ci		goto out_restart;
1548c2ecf20Sopenharmony_ci	if (cdev->private->dma_area->senseid.cu_type == 0xffff)
1558c2ecf20Sopenharmony_ci		goto out_restart;
1568c2ecf20Sopenharmony_ci	/* Check for incompatible SENSE ID data. */
1578c2ecf20Sopenharmony_ci	if (cdev->private->dma_area->senseid.reserved != 0xff)
1588c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
1598c2ecf20Sopenharmony_ci	/* Check for extended-identification information. */
1608c2ecf20Sopenharmony_ci	if (len > SENSE_ID_BASIC_LEN)
1618c2ecf20Sopenharmony_ci		cdev->private->flags.esid = 1;
1628c2ecf20Sopenharmony_ci	return 0;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ciout_restart:
1658c2ecf20Sopenharmony_ci	snsid_init(cdev);
1668c2ecf20Sopenharmony_ci	return -EAGAIN;
1678c2ecf20Sopenharmony_ci}
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci/*
1708c2ecf20Sopenharmony_ci * Process SENSE ID request result.
1718c2ecf20Sopenharmony_ci */
1728c2ecf20Sopenharmony_cistatic void snsid_callback(struct ccw_device *cdev, void *data, int rc)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	struct ccw_dev_id *id = &cdev->private->dev_id;
1758c2ecf20Sopenharmony_ci	struct senseid *senseid = &cdev->private->dma_area->senseid;
1768c2ecf20Sopenharmony_ci	int vm = 0;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	if (rc && MACHINE_IS_VM) {
1798c2ecf20Sopenharmony_ci		/* Try diag 0x210 fallback on z/VM. */
1808c2ecf20Sopenharmony_ci		snsid_init(cdev);
1818c2ecf20Sopenharmony_ci		if (diag210_get_dev_info(cdev) == 0) {
1828c2ecf20Sopenharmony_ci			rc = 0;
1838c2ecf20Sopenharmony_ci			vm = 1;
1848c2ecf20Sopenharmony_ci		}
1858c2ecf20Sopenharmony_ci	}
1868c2ecf20Sopenharmony_ci	CIO_MSG_EVENT(2, "snsid: device 0.%x.%04x: rc=%d %04x/%02x "
1878c2ecf20Sopenharmony_ci		      "%04x/%02x%s\n", id->ssid, id->devno, rc,
1888c2ecf20Sopenharmony_ci		      senseid->cu_type, senseid->cu_model, senseid->dev_type,
1898c2ecf20Sopenharmony_ci		      senseid->dev_model, vm ? " (diag210)" : "");
1908c2ecf20Sopenharmony_ci	ccw_device_sense_id_done(cdev, rc);
1918c2ecf20Sopenharmony_ci}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci/**
1948c2ecf20Sopenharmony_ci * ccw_device_sense_id_start - perform SENSE ID
1958c2ecf20Sopenharmony_ci * @cdev: ccw device
1968c2ecf20Sopenharmony_ci *
1978c2ecf20Sopenharmony_ci * Execute a SENSE ID channel program on @cdev to update its sense id
1988c2ecf20Sopenharmony_ci * information. When finished, call ccw_device_sense_id_done with a
1998c2ecf20Sopenharmony_ci * return code specifying the result.
2008c2ecf20Sopenharmony_ci */
2018c2ecf20Sopenharmony_civoid ccw_device_sense_id_start(struct ccw_device *cdev)
2028c2ecf20Sopenharmony_ci{
2038c2ecf20Sopenharmony_ci	struct subchannel *sch = to_subchannel(cdev->dev.parent);
2048c2ecf20Sopenharmony_ci	struct ccw_request *req = &cdev->private->req;
2058c2ecf20Sopenharmony_ci	struct ccw1 *cp = cdev->private->dma_area->iccws;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	CIO_TRACE_EVENT(4, "snsid");
2088c2ecf20Sopenharmony_ci	CIO_HEX_EVENT(4, &cdev->private->dev_id, sizeof(cdev->private->dev_id));
2098c2ecf20Sopenharmony_ci	/* Data setup. */
2108c2ecf20Sopenharmony_ci	snsid_init(cdev);
2118c2ecf20Sopenharmony_ci	/* Channel program setup. */
2128c2ecf20Sopenharmony_ci	cp->cmd_code	= CCW_CMD_SENSE_ID;
2138c2ecf20Sopenharmony_ci	cp->cda		= (u32) (addr_t) &cdev->private->dma_area->senseid;
2148c2ecf20Sopenharmony_ci	cp->count	= sizeof(struct senseid);
2158c2ecf20Sopenharmony_ci	cp->flags	= CCW_FLAG_SLI;
2168c2ecf20Sopenharmony_ci	/* Request setup. */
2178c2ecf20Sopenharmony_ci	memset(req, 0, sizeof(*req));
2188c2ecf20Sopenharmony_ci	req->cp		= cp;
2198c2ecf20Sopenharmony_ci	req->timeout	= SENSE_ID_TIMEOUT;
2208c2ecf20Sopenharmony_ci	req->maxretries	= SENSE_ID_RETRIES;
2218c2ecf20Sopenharmony_ci	req->lpm	= sch->schib.pmcw.pam & sch->opm;
2228c2ecf20Sopenharmony_ci	req->check	= snsid_check;
2238c2ecf20Sopenharmony_ci	req->callback	= snsid_callback;
2248c2ecf20Sopenharmony_ci	ccw_request_start(cdev);
2258c2ecf20Sopenharmony_ci}
226