387 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Driver for s390 eadm subchannels4 *5 * Copyright IBM Corp. 20126 * Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com>7 */8 9#include <linux/kernel_stat.h>10#include <linux/completion.h>11#include <linux/workqueue.h>12#include <linux/spinlock.h>13#include <linux/device.h>14#include <linux/module.h>15#include <linux/timer.h>16#include <linux/slab.h>17#include <linux/list.h>18#include <linux/io.h>19 20#include <asm/css_chars.h>21#include <asm/debug.h>22#include <asm/isc.h>23#include <asm/cio.h>24#include <asm/scsw.h>25#include <asm/eadm.h>26 27#include "eadm_sch.h"28#include "ioasm.h"29#include "cio.h"30#include "css.h"31#include "orb.h"32 33MODULE_DESCRIPTION("driver for s390 eadm subchannels");34MODULE_LICENSE("GPL");35 36#define EADM_TIMEOUT (7 * HZ)37static DEFINE_SPINLOCK(list_lock);38static LIST_HEAD(eadm_list);39 40static debug_info_t *eadm_debug;41 42#define EADM_LOG(imp, txt) do { \43 debug_text_event(eadm_debug, imp, txt); \44 } while (0)45 46static void EADM_LOG_HEX(int level, void *data, int length)47{48 debug_event(eadm_debug, level, data, length);49}50 51static void orb_init(union orb *orb)52{53 memset(orb, 0, sizeof(union orb));54 orb->eadm.compat1 = 1;55 orb->eadm.compat2 = 1;56 orb->eadm.fmt = 1;57 orb->eadm.x = 1;58}59 60static int eadm_subchannel_start(struct subchannel *sch, struct aob *aob)61{62 union orb *orb = &get_eadm_private(sch)->orb;63 int cc;64 65 orb_init(orb);66 orb->eadm.aob = virt_to_dma32(aob);67 orb->eadm.intparm = (u32)virt_to_phys(sch);68 orb->eadm.key = PAGE_DEFAULT_KEY >> 4;69 70 EADM_LOG(6, "start");71 EADM_LOG_HEX(6, &sch->schid, sizeof(sch->schid));72 73 cc = ssch(sch->schid, orb);74 switch (cc) {75 case 0:76 sch->schib.scsw.eadm.actl |= SCSW_ACTL_START_PEND;77 break;78 case 1: /* status pending */79 case 2: /* busy */80 return -EBUSY;81 case 3: /* not operational */82 return -ENODEV;83 }84 return 0;85}86 87static int eadm_subchannel_clear(struct subchannel *sch)88{89 int cc;90 91 cc = csch(sch->schid);92 if (cc)93 return -ENODEV;94 95 sch->schib.scsw.eadm.actl |= SCSW_ACTL_CLEAR_PEND;96 return 0;97}98 99static void eadm_subchannel_timeout(struct timer_list *t)100{101 struct eadm_private *private = from_timer(private, t, timer);102 struct subchannel *sch = private->sch;103 104 spin_lock_irq(&sch->lock);105 EADM_LOG(1, "timeout");106 EADM_LOG_HEX(1, &sch->schid, sizeof(sch->schid));107 if (eadm_subchannel_clear(sch))108 EADM_LOG(0, "clear failed");109 spin_unlock_irq(&sch->lock);110}111 112static void eadm_subchannel_set_timeout(struct subchannel *sch, int expires)113{114 struct eadm_private *private = get_eadm_private(sch);115 116 if (expires == 0)117 del_timer(&private->timer);118 else119 mod_timer(&private->timer, jiffies + expires);120}121 122static void eadm_subchannel_irq(struct subchannel *sch)123{124 struct eadm_private *private = get_eadm_private(sch);125 struct eadm_scsw *scsw = &sch->schib.scsw.eadm;126 struct irb *irb = this_cpu_ptr(&cio_irb);127 blk_status_t error = BLK_STS_OK;128 129 EADM_LOG(6, "irq");130 EADM_LOG_HEX(6, irb, sizeof(*irb));131 132 inc_irq_stat(IRQIO_ADM);133 134 if ((scsw->stctl & (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND))135 && scsw->eswf == 1 && irb->esw.eadm.erw.r)136 error = BLK_STS_IOERR;137 138 if (scsw->fctl & SCSW_FCTL_CLEAR_FUNC)139 error = BLK_STS_TIMEOUT;140 141 eadm_subchannel_set_timeout(sch, 0);142 143 if (private->state != EADM_BUSY) {144 EADM_LOG(1, "irq unsol");145 EADM_LOG_HEX(1, irb, sizeof(*irb));146 private->state = EADM_NOT_OPER;147 css_sched_sch_todo(sch, SCH_TODO_EVAL);148 return;149 }150 scm_irq_handler(dma32_to_virt(scsw->aob), error);151 private->state = EADM_IDLE;152 153 if (private->completion)154 complete(private->completion);155}156 157static struct subchannel *eadm_get_idle_sch(void)158{159 struct eadm_private *private;160 struct subchannel *sch;161 unsigned long flags;162 163 spin_lock_irqsave(&list_lock, flags);164 list_for_each_entry(private, &eadm_list, head) {165 sch = private->sch;166 spin_lock(&sch->lock);167 if (private->state == EADM_IDLE) {168 private->state = EADM_BUSY;169 list_move_tail(&private->head, &eadm_list);170 spin_unlock(&sch->lock);171 spin_unlock_irqrestore(&list_lock, flags);172 173 return sch;174 }175 spin_unlock(&sch->lock);176 }177 spin_unlock_irqrestore(&list_lock, flags);178 179 return NULL;180}181 182int eadm_start_aob(struct aob *aob)183{184 struct eadm_private *private;185 struct subchannel *sch;186 unsigned long flags;187 int ret;188 189 sch = eadm_get_idle_sch();190 if (!sch)191 return -EBUSY;192 193 spin_lock_irqsave(&sch->lock, flags);194 eadm_subchannel_set_timeout(sch, EADM_TIMEOUT);195 ret = eadm_subchannel_start(sch, aob);196 if (!ret)197 goto out_unlock;198 199 /* Handle start subchannel failure. */200 eadm_subchannel_set_timeout(sch, 0);201 private = get_eadm_private(sch);202 private->state = EADM_NOT_OPER;203 css_sched_sch_todo(sch, SCH_TODO_EVAL);204 205out_unlock:206 spin_unlock_irqrestore(&sch->lock, flags);207 208 return ret;209}210EXPORT_SYMBOL_GPL(eadm_start_aob);211 212static int eadm_subchannel_probe(struct subchannel *sch)213{214 struct eadm_private *private;215 int ret;216 217 private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA);218 if (!private)219 return -ENOMEM;220 221 INIT_LIST_HEAD(&private->head);222 timer_setup(&private->timer, eadm_subchannel_timeout, 0);223 224 spin_lock_irq(&sch->lock);225 set_eadm_private(sch, private);226 private->state = EADM_IDLE;227 private->sch = sch;228 sch->isc = EADM_SCH_ISC;229 ret = cio_enable_subchannel(sch, (u32)virt_to_phys(sch));230 if (ret) {231 set_eadm_private(sch, NULL);232 spin_unlock_irq(&sch->lock);233 kfree(private);234 goto out;235 }236 spin_unlock_irq(&sch->lock);237 238 spin_lock_irq(&list_lock);239 list_add(&private->head, &eadm_list);240 spin_unlock_irq(&list_lock);241out:242 return ret;243}244 245static void eadm_quiesce(struct subchannel *sch)246{247 struct eadm_private *private = get_eadm_private(sch);248 DECLARE_COMPLETION_ONSTACK(completion);249 int ret;250 251 spin_lock_irq(&sch->lock);252 if (private->state != EADM_BUSY)253 goto disable;254 255 if (eadm_subchannel_clear(sch))256 goto disable;257 258 private->completion = &completion;259 spin_unlock_irq(&sch->lock);260 261 wait_for_completion_io(&completion);262 263 spin_lock_irq(&sch->lock);264 private->completion = NULL;265 266disable:267 eadm_subchannel_set_timeout(sch, 0);268 do {269 ret = cio_disable_subchannel(sch);270 } while (ret == -EBUSY);271 272 spin_unlock_irq(&sch->lock);273}274 275static void eadm_subchannel_remove(struct subchannel *sch)276{277 struct eadm_private *private = get_eadm_private(sch);278 279 spin_lock_irq(&list_lock);280 list_del(&private->head);281 spin_unlock_irq(&list_lock);282 283 eadm_quiesce(sch);284 285 spin_lock_irq(&sch->lock);286 set_eadm_private(sch, NULL);287 spin_unlock_irq(&sch->lock);288 289 kfree(private);290}291 292static void eadm_subchannel_shutdown(struct subchannel *sch)293{294 eadm_quiesce(sch);295}296 297/**298 * eadm_subchannel_sch_event - process subchannel event299 * @sch: subchannel300 * @process: non-zero if function is called in process context301 *302 * An unspecified event occurred for this subchannel. Adjust data according303 * to the current operational state of the subchannel. Return zero when the304 * event has been handled sufficiently or -EAGAIN when this function should305 * be called again in process context.306 */307static int eadm_subchannel_sch_event(struct subchannel *sch, int process)308{309 struct eadm_private *private;310 unsigned long flags;311 312 spin_lock_irqsave(&sch->lock, flags);313 if (!device_is_registered(&sch->dev))314 goto out_unlock;315 316 if (work_pending(&sch->todo_work))317 goto out_unlock;318 319 if (cio_update_schib(sch)) {320 css_sched_sch_todo(sch, SCH_TODO_UNREG);321 goto out_unlock;322 }323 private = get_eadm_private(sch);324 if (private->state == EADM_NOT_OPER)325 private->state = EADM_IDLE;326 327out_unlock:328 spin_unlock_irqrestore(&sch->lock, flags);329 330 return 0;331}332 333static struct css_device_id eadm_subchannel_ids[] = {334 { .match_flags = 0x1, .type = SUBCHANNEL_TYPE_ADM, },335 { /* end of list */ },336};337MODULE_DEVICE_TABLE(css, eadm_subchannel_ids);338 339static struct css_driver eadm_subchannel_driver = {340 .drv = {341 .name = "eadm_subchannel",342 .owner = THIS_MODULE,343 },344 .subchannel_type = eadm_subchannel_ids,345 .irq = eadm_subchannel_irq,346 .probe = eadm_subchannel_probe,347 .remove = eadm_subchannel_remove,348 .shutdown = eadm_subchannel_shutdown,349 .sch_event = eadm_subchannel_sch_event,350};351 352static int __init eadm_sch_init(void)353{354 int ret;355 356 if (!css_general_characteristics.eadm)357 return -ENXIO;358 359 eadm_debug = debug_register("eadm_log", 16, 1, 16);360 if (!eadm_debug)361 return -ENOMEM;362 363 debug_register_view(eadm_debug, &debug_hex_ascii_view);364 debug_set_level(eadm_debug, 2);365 366 isc_register(EADM_SCH_ISC);367 ret = css_driver_register(&eadm_subchannel_driver);368 if (ret)369 goto cleanup;370 371 return ret;372 373cleanup:374 isc_unregister(EADM_SCH_ISC);375 debug_unregister(eadm_debug);376 return ret;377}378 379static void __exit eadm_sch_exit(void)380{381 css_driver_unregister(&eadm_subchannel_driver);382 isc_unregister(EADM_SCH_ISC);383 debug_unregister(eadm_debug);384}385module_init(eadm_sch_init);386module_exit(eadm_sch_exit);387