1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * CCW device SENSE ID I/O handling. |
4 | * |
5 | * Copyright IBM Corp. 2002, 2009 |
6 | * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com> |
7 | * Martin Schwidefsky <schwidefsky@de.ibm.com> |
8 | * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> |
9 | */ |
10 | |
11 | #include <linux/kernel.h> |
12 | #include <linux/string.h> |
13 | #include <linux/types.h> |
14 | #include <linux/errno.h> |
15 | #include <asm/ccwdev.h> |
16 | #include <asm/setup.h> |
17 | #include <asm/cio.h> |
18 | #include <asm/diag.h> |
19 | |
20 | #include "cio.h" |
21 | #include "cio_debug.h" |
22 | #include "device.h" |
23 | #include "io_sch.h" |
24 | |
25 | #define SENSE_ID_RETRIES 256 |
26 | #define SENSE_ID_TIMEOUT (10 * HZ) |
27 | #define SENSE_ID_MIN_LEN 4 |
28 | #define SENSE_ID_BASIC_LEN 7 |
29 | |
30 | /** |
31 | * diag210_to_senseid - convert diag 0x210 data to sense id information |
32 | * @senseid: sense id |
33 | * @diag: diag 0x210 data |
34 | * |
35 | * Return 0 on success, non-zero otherwise. |
36 | */ |
37 | static int diag210_to_senseid(struct senseid *senseid, struct diag210 *diag) |
38 | { |
39 | static struct { |
40 | int class, type, cu_type; |
41 | } vm_devices[] = { |
42 | { .class: 0x08, .type: 0x01, .cu_type: 0x3480 }, |
43 | { 0x08, 0x02, 0x3430 }, |
44 | { 0x08, 0x10, 0x3420 }, |
45 | { 0x08, 0x42, 0x3424 }, |
46 | { 0x08, 0x44, 0x9348 }, |
47 | { 0x08, 0x81, 0x3490 }, |
48 | { 0x08, 0x82, 0x3422 }, |
49 | { 0x10, 0x41, 0x1403 }, |
50 | { 0x10, 0x42, 0x3211 }, |
51 | { 0x10, 0x43, 0x3203 }, |
52 | { 0x10, 0x45, 0x3800 }, |
53 | { 0x10, 0x47, 0x3262 }, |
54 | { 0x10, 0x48, 0x3820 }, |
55 | { 0x10, 0x49, 0x3800 }, |
56 | { 0x10, 0x4a, 0x4245 }, |
57 | { 0x10, 0x4b, 0x4248 }, |
58 | { 0x10, 0x4d, 0x3800 }, |
59 | { 0x10, 0x4e, 0x3820 }, |
60 | { 0x10, 0x4f, 0x3820 }, |
61 | { 0x10, 0x82, 0x2540 }, |
62 | { 0x10, 0x84, 0x3525 }, |
63 | { 0x20, 0x81, 0x2501 }, |
64 | { 0x20, 0x82, 0x2540 }, |
65 | { 0x20, 0x84, 0x3505 }, |
66 | { 0x40, 0x01, 0x3278 }, |
67 | { 0x40, 0x04, 0x3277 }, |
68 | { 0x40, 0x80, 0x2250 }, |
69 | { 0x40, 0xc0, 0x5080 }, |
70 | { 0x80, 0x00, 0x3215 }, |
71 | }; |
72 | int i; |
73 | |
74 | /* Special case for osa devices. */ |
75 | if (diag->vrdcvcla == 0x02 && diag->vrdcvtyp == 0x20) { |
76 | senseid->cu_type = 0x3088; |
77 | senseid->cu_model = 0x60; |
78 | senseid->reserved = 0xff; |
79 | return 0; |
80 | } |
81 | for (i = 0; i < ARRAY_SIZE(vm_devices); i++) { |
82 | if (diag->vrdcvcla == vm_devices[i].class && |
83 | diag->vrdcvtyp == vm_devices[i].type) { |
84 | senseid->cu_type = vm_devices[i].cu_type; |
85 | senseid->reserved = 0xff; |
86 | return 0; |
87 | } |
88 | } |
89 | |
90 | return -ENODEV; |
91 | } |
92 | |
93 | /** |
94 | * diag210_get_dev_info - retrieve device information via diag 0x210 |
95 | * @cdev: ccw device |
96 | * |
97 | * Returns zero on success, non-zero otherwise. |
98 | */ |
99 | static int diag210_get_dev_info(struct ccw_device *cdev) |
100 | { |
101 | struct ccw_dev_id *dev_id = &cdev->private->dev_id; |
102 | struct senseid *senseid = &cdev->private->dma_area->senseid; |
103 | struct diag210 diag_data; |
104 | int rc; |
105 | |
106 | if (dev_id->ssid != 0) |
107 | return -ENODEV; |
108 | memset(&diag_data, 0, sizeof(diag_data)); |
109 | diag_data.vrdcdvno = dev_id->devno; |
110 | diag_data.vrdclen = sizeof(diag_data); |
111 | rc = diag210(&diag_data); |
112 | CIO_TRACE_EVENT(4, "diag210" ); |
113 | CIO_HEX_EVENT(level: 4, data: &rc, length: sizeof(rc)); |
114 | CIO_HEX_EVENT(level: 4, data: &diag_data, length: sizeof(diag_data)); |
115 | if (rc != 0 && rc != 2) |
116 | goto err_failed; |
117 | if (diag210_to_senseid(senseid, diag: &diag_data)) |
118 | goto err_unknown; |
119 | return 0; |
120 | |
121 | err_unknown: |
122 | CIO_MSG_EVENT(0, "snsid: device 0.%x.%04x: unknown diag210 data\n" , |
123 | dev_id->ssid, dev_id->devno); |
124 | return -ENODEV; |
125 | err_failed: |
126 | CIO_MSG_EVENT(0, "snsid: device 0.%x.%04x: diag210 failed (rc=%d)\n" , |
127 | dev_id->ssid, dev_id->devno, rc); |
128 | return -ENODEV; |
129 | } |
130 | |
131 | /* |
132 | * Initialize SENSE ID data. |
133 | */ |
134 | static void snsid_init(struct ccw_device *cdev) |
135 | { |
136 | cdev->private->flags.esid = 0; |
137 | |
138 | memset(&cdev->private->dma_area->senseid, 0, |
139 | sizeof(cdev->private->dma_area->senseid)); |
140 | cdev->private->dma_area->senseid.cu_type = 0xffff; |
141 | } |
142 | |
143 | /* |
144 | * Check for complete SENSE ID data. |
145 | */ |
146 | static int snsid_check(struct ccw_device *cdev, void *data) |
147 | { |
148 | struct cmd_scsw *scsw = &cdev->private->dma_area->irb.scsw.cmd; |
149 | int len = sizeof(struct senseid) - scsw->count; |
150 | |
151 | /* Check for incomplete SENSE ID data. */ |
152 | if (len < SENSE_ID_MIN_LEN) |
153 | goto out_restart; |
154 | if (cdev->private->dma_area->senseid.cu_type == 0xffff) |
155 | goto out_restart; |
156 | /* Check for incompatible SENSE ID data. */ |
157 | if (cdev->private->dma_area->senseid.reserved != 0xff) |
158 | return -EOPNOTSUPP; |
159 | /* Check for extended-identification information. */ |
160 | if (len > SENSE_ID_BASIC_LEN) |
161 | cdev->private->flags.esid = 1; |
162 | return 0; |
163 | |
164 | out_restart: |
165 | snsid_init(cdev); |
166 | return -EAGAIN; |
167 | } |
168 | |
169 | /* |
170 | * Process SENSE ID request result. |
171 | */ |
172 | static void snsid_callback(struct ccw_device *cdev, void *data, int rc) |
173 | { |
174 | struct ccw_dev_id *id = &cdev->private->dev_id; |
175 | struct senseid *senseid = &cdev->private->dma_area->senseid; |
176 | int vm = 0; |
177 | |
178 | if (rc && MACHINE_IS_VM) { |
179 | /* Try diag 0x210 fallback on z/VM. */ |
180 | snsid_init(cdev); |
181 | if (diag210_get_dev_info(cdev) == 0) { |
182 | rc = 0; |
183 | vm = 1; |
184 | } |
185 | } |
186 | CIO_MSG_EVENT(2, "snsid: device 0.%x.%04x: rc=%d %04x/%02x " |
187 | "%04x/%02x%s\n" , id->ssid, id->devno, rc, |
188 | senseid->cu_type, senseid->cu_model, senseid->dev_type, |
189 | senseid->dev_model, vm ? " (diag210)" : "" ); |
190 | ccw_device_sense_id_done(cdev, rc); |
191 | } |
192 | |
193 | /** |
194 | * ccw_device_sense_id_start - perform SENSE ID |
195 | * @cdev: ccw device |
196 | * |
197 | * Execute a SENSE ID channel program on @cdev to update its sense id |
198 | * information. When finished, call ccw_device_sense_id_done with a |
199 | * return code specifying the result. |
200 | */ |
201 | void ccw_device_sense_id_start(struct ccw_device *cdev) |
202 | { |
203 | struct subchannel *sch = to_subchannel(cdev->dev.parent); |
204 | struct ccw_request *req = &cdev->private->req; |
205 | struct ccw1 *cp = cdev->private->dma_area->iccws; |
206 | |
207 | CIO_TRACE_EVENT(4, "snsid" ); |
208 | CIO_HEX_EVENT(level: 4, data: &cdev->private->dev_id, length: sizeof(cdev->private->dev_id)); |
209 | /* Data setup. */ |
210 | snsid_init(cdev); |
211 | /* Channel program setup. */ |
212 | cp->cmd_code = CCW_CMD_SENSE_ID; |
213 | cp->cda = (u32)virt_to_phys(&cdev->private->dma_area->senseid); |
214 | cp->count = sizeof(struct senseid); |
215 | cp->flags = CCW_FLAG_SLI; |
216 | /* Request setup. */ |
217 | memset(req, 0, sizeof(*req)); |
218 | req->cp = cp; |
219 | req->timeout = SENSE_ID_TIMEOUT; |
220 | req->maxretries = SENSE_ID_RETRIES; |
221 | req->lpm = sch->schib.pmcw.pam & sch->opm; |
222 | req->check = snsid_check; |
223 | req->callback = snsid_callback; |
224 | ccw_request_start(cdev); |
225 | } |
226 | |