1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * xen console driver interface to hvc_console.c |
4 | * |
5 | * (c) 2007 Gerd Hoffmann <kraxel@suse.de> |
6 | */ |
7 | |
8 | #include <linux/console.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/err.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/init.h> |
13 | #include <linux/types.h> |
14 | #include <linux/list.h> |
15 | #include <linux/serial_core.h> |
16 | |
17 | #include <asm/io.h> |
18 | #include <asm/xen/hypervisor.h> |
19 | |
20 | #include <xen/xen.h> |
21 | #include <xen/interface/xen.h> |
22 | #include <xen/hvm.h> |
23 | #include <xen/grant_table.h> |
24 | #include <xen/page.h> |
25 | #include <xen/events.h> |
26 | #include <xen/interface/io/console.h> |
27 | #include <xen/interface/sched.h> |
28 | #include <xen/hvc-console.h> |
29 | #include <xen/xenbus.h> |
30 | |
31 | #include "hvc_console.h" |
32 | |
33 | #define HVC_COOKIE 0x58656e /* "Xen" in hex */ |
34 | |
35 | struct xencons_info { |
36 | struct list_head list; |
37 | struct xenbus_device *xbdev; |
38 | struct xencons_interface *intf; |
39 | unsigned int evtchn; |
40 | XENCONS_RING_IDX out_cons; |
41 | unsigned int out_cons_same; |
42 | struct hvc_struct *hvc; |
43 | int irq; |
44 | int vtermno; |
45 | grant_ref_t gntref; |
46 | spinlock_t ring_lock; |
47 | }; |
48 | |
49 | static LIST_HEAD(xenconsoles); |
50 | static DEFINE_SPINLOCK(xencons_lock); |
51 | |
52 | /* ------------------------------------------------------------------ */ |
53 | |
54 | static struct xencons_info *vtermno_to_xencons(int vtermno) |
55 | { |
56 | struct xencons_info *entry, *ret = NULL; |
57 | unsigned long flags; |
58 | |
59 | spin_lock_irqsave(&xencons_lock, flags); |
60 | if (list_empty(head: &xenconsoles)) { |
61 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
62 | return NULL; |
63 | } |
64 | |
65 | list_for_each_entry(entry, &xenconsoles, list) { |
66 | if (entry->vtermno == vtermno) { |
67 | ret = entry; |
68 | break; |
69 | } |
70 | } |
71 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
72 | |
73 | return ret; |
74 | } |
75 | |
76 | static inline int xenbus_devid_to_vtermno(int devid) |
77 | { |
78 | return devid + HVC_COOKIE; |
79 | } |
80 | |
81 | static inline void notify_daemon(struct xencons_info *cons) |
82 | { |
83 | /* Use evtchn: this is called early, before irq is set up. */ |
84 | notify_remote_via_evtchn(port: cons->evtchn); |
85 | } |
86 | |
87 | static int __write_console(struct xencons_info *xencons, |
88 | const char *data, int len) |
89 | { |
90 | XENCONS_RING_IDX cons, prod; |
91 | struct xencons_interface *intf = xencons->intf; |
92 | int sent = 0; |
93 | unsigned long flags; |
94 | |
95 | spin_lock_irqsave(&xencons->ring_lock, flags); |
96 | cons = intf->out_cons; |
97 | prod = intf->out_prod; |
98 | mb(); /* update queue values before going on */ |
99 | |
100 | if ((prod - cons) > sizeof(intf->out)) { |
101 | spin_unlock_irqrestore(lock: &xencons->ring_lock, flags); |
102 | pr_err_once("xencons: Illegal ring page indices" ); |
103 | return -EINVAL; |
104 | } |
105 | |
106 | while ((sent < len) && ((prod - cons) < sizeof(intf->out))) |
107 | intf->out[MASK_XENCONS_IDX(prod++, intf->out)] = data[sent++]; |
108 | |
109 | wmb(); /* write ring before updating pointer */ |
110 | intf->out_prod = prod; |
111 | spin_unlock_irqrestore(lock: &xencons->ring_lock, flags); |
112 | |
113 | if (sent) |
114 | notify_daemon(cons: xencons); |
115 | return sent; |
116 | } |
117 | |
118 | static int domU_write_console(uint32_t vtermno, const char *data, int len) |
119 | { |
120 | int ret = len; |
121 | struct xencons_info *cons = vtermno_to_xencons(vtermno); |
122 | if (cons == NULL) |
123 | return -EINVAL; |
124 | |
125 | /* |
126 | * Make sure the whole buffer is emitted, polling if |
127 | * necessary. We don't ever want to rely on the hvc daemon |
128 | * because the most interesting console output is when the |
129 | * kernel is crippled. |
130 | */ |
131 | while (len) { |
132 | int sent = __write_console(xencons: cons, data, len); |
133 | |
134 | if (sent < 0) |
135 | return sent; |
136 | |
137 | data += sent; |
138 | len -= sent; |
139 | |
140 | if (unlikely(len)) |
141 | HYPERVISOR_sched_op(SCHEDOP_yield, NULL); |
142 | } |
143 | |
144 | return ret; |
145 | } |
146 | |
147 | static int domU_read_console(uint32_t vtermno, char *buf, int len) |
148 | { |
149 | struct xencons_interface *intf; |
150 | XENCONS_RING_IDX cons, prod; |
151 | int recv = 0; |
152 | struct xencons_info *xencons = vtermno_to_xencons(vtermno); |
153 | unsigned int eoiflag = 0; |
154 | unsigned long flags; |
155 | |
156 | if (xencons == NULL) |
157 | return -EINVAL; |
158 | intf = xencons->intf; |
159 | |
160 | spin_lock_irqsave(&xencons->ring_lock, flags); |
161 | cons = intf->in_cons; |
162 | prod = intf->in_prod; |
163 | mb(); /* get pointers before reading ring */ |
164 | |
165 | if ((prod - cons) > sizeof(intf->in)) { |
166 | spin_unlock_irqrestore(lock: &xencons->ring_lock, flags); |
167 | pr_err_once("xencons: Illegal ring page indices" ); |
168 | return -EINVAL; |
169 | } |
170 | |
171 | while (cons != prod && recv < len) |
172 | buf[recv++] = intf->in[MASK_XENCONS_IDX(cons++, intf->in)]; |
173 | |
174 | mb(); /* read ring before consuming */ |
175 | intf->in_cons = cons; |
176 | |
177 | /* |
178 | * When to mark interrupt having been spurious: |
179 | * - there was no new data to be read, and |
180 | * - the backend did not consume some output bytes, and |
181 | * - the previous round with no read data didn't see consumed bytes |
182 | * (we might have a race with an interrupt being in flight while |
183 | * updating xencons->out_cons, so account for that by allowing one |
184 | * round without any visible reason) |
185 | */ |
186 | if (intf->out_cons != xencons->out_cons) { |
187 | xencons->out_cons = intf->out_cons; |
188 | xencons->out_cons_same = 0; |
189 | } |
190 | if (!recv && xencons->out_cons_same++ > 1) { |
191 | eoiflag = XEN_EOI_FLAG_SPURIOUS; |
192 | } |
193 | spin_unlock_irqrestore(lock: &xencons->ring_lock, flags); |
194 | |
195 | if (recv) { |
196 | notify_daemon(cons: xencons); |
197 | } |
198 | |
199 | xen_irq_lateeoi(irq: xencons->irq, eoi_flags: eoiflag); |
200 | |
201 | return recv; |
202 | } |
203 | |
204 | static const struct hv_ops domU_hvc_ops = { |
205 | .get_chars = domU_read_console, |
206 | .put_chars = domU_write_console, |
207 | .notifier_add = notifier_add_irq, |
208 | .notifier_del = notifier_del_irq, |
209 | .notifier_hangup = notifier_hangup_irq, |
210 | }; |
211 | |
212 | static int dom0_read_console(uint32_t vtermno, char *buf, int len) |
213 | { |
214 | return HYPERVISOR_console_io(CONSOLEIO_read, count: len, str: buf); |
215 | } |
216 | |
217 | /* |
218 | * Either for a dom0 to write to the system console, or a domU with a |
219 | * debug version of Xen |
220 | */ |
221 | static int dom0_write_console(uint32_t vtermno, const char *str, int len) |
222 | { |
223 | int rc = HYPERVISOR_console_io(CONSOLEIO_write, count: len, str: (char *)str); |
224 | if (rc < 0) |
225 | return rc; |
226 | |
227 | return len; |
228 | } |
229 | |
230 | static const struct hv_ops dom0_hvc_ops = { |
231 | .get_chars = dom0_read_console, |
232 | .put_chars = dom0_write_console, |
233 | .notifier_add = notifier_add_irq, |
234 | .notifier_del = notifier_del_irq, |
235 | .notifier_hangup = notifier_hangup_irq, |
236 | }; |
237 | |
238 | static int xen_hvm_console_init(void) |
239 | { |
240 | int r; |
241 | uint64_t v = 0; |
242 | unsigned long gfn, flags; |
243 | struct xencons_info *info; |
244 | |
245 | if (!xen_hvm_domain()) |
246 | return -ENODEV; |
247 | |
248 | info = vtermno_to_xencons(HVC_COOKIE); |
249 | if (!info) { |
250 | info = kzalloc(size: sizeof(struct xencons_info), GFP_KERNEL); |
251 | if (!info) |
252 | return -ENOMEM; |
253 | spin_lock_init(&info->ring_lock); |
254 | } else if (info->intf != NULL) { |
255 | /* already configured */ |
256 | return 0; |
257 | } |
258 | /* |
259 | * If the toolstack (or the hypervisor) hasn't set these values, the |
260 | * default value is 0. Even though gfn = 0 and evtchn = 0 are |
261 | * theoretically correct values, in practice they never are and they |
262 | * mean that a legacy toolstack hasn't initialized the pv console correctly. |
263 | */ |
264 | r = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, value: &v); |
265 | if (r < 0 || v == 0) |
266 | goto err; |
267 | info->evtchn = v; |
268 | v = 0; |
269 | r = hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, value: &v); |
270 | if (r < 0 || v == 0) |
271 | goto err; |
272 | gfn = v; |
273 | info->intf = memremap(offset: gfn << XEN_PAGE_SHIFT, XEN_PAGE_SIZE, flags: MEMREMAP_WB); |
274 | if (info->intf == NULL) |
275 | goto err; |
276 | info->vtermno = HVC_COOKIE; |
277 | |
278 | spin_lock_irqsave(&xencons_lock, flags); |
279 | list_add_tail(new: &info->list, head: &xenconsoles); |
280 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
281 | |
282 | return 0; |
283 | err: |
284 | kfree(objp: info); |
285 | return -ENODEV; |
286 | } |
287 | |
288 | static int xencons_info_pv_init(struct xencons_info *info, int vtermno) |
289 | { |
290 | spin_lock_init(&info->ring_lock); |
291 | info->evtchn = xen_start_info->console.domU.evtchn; |
292 | /* GFN == MFN for PV guest */ |
293 | info->intf = gfn_to_virt(xen_start_info->console.domU.mfn); |
294 | info->vtermno = vtermno; |
295 | |
296 | list_add_tail(new: &info->list, head: &xenconsoles); |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static int xen_pv_console_init(void) |
302 | { |
303 | struct xencons_info *info; |
304 | unsigned long flags; |
305 | |
306 | if (!xen_pv_domain()) |
307 | return -ENODEV; |
308 | |
309 | if (!xen_start_info->console.domU.evtchn) |
310 | return -ENODEV; |
311 | |
312 | info = vtermno_to_xencons(HVC_COOKIE); |
313 | if (!info) { |
314 | info = kzalloc(size: sizeof(struct xencons_info), GFP_KERNEL); |
315 | if (!info) |
316 | return -ENOMEM; |
317 | } else if (info->intf != NULL) { |
318 | /* already configured */ |
319 | return 0; |
320 | } |
321 | spin_lock_irqsave(&xencons_lock, flags); |
322 | xencons_info_pv_init(info, HVC_COOKIE); |
323 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
324 | |
325 | return 0; |
326 | } |
327 | |
328 | static int xen_initial_domain_console_init(void) |
329 | { |
330 | struct xencons_info *info; |
331 | unsigned long flags; |
332 | |
333 | if (!xen_initial_domain()) |
334 | return -ENODEV; |
335 | |
336 | info = vtermno_to_xencons(HVC_COOKIE); |
337 | if (!info) { |
338 | info = kzalloc(size: sizeof(struct xencons_info), GFP_KERNEL); |
339 | if (!info) |
340 | return -ENOMEM; |
341 | spin_lock_init(&info->ring_lock); |
342 | } |
343 | |
344 | info->irq = bind_virq_to_irq(VIRQ_CONSOLE, cpu: 0, percpu: false); |
345 | info->vtermno = HVC_COOKIE; |
346 | |
347 | spin_lock_irqsave(&xencons_lock, flags); |
348 | list_add_tail(new: &info->list, head: &xenconsoles); |
349 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | static void xen_console_update_evtchn(struct xencons_info *info) |
355 | { |
356 | if (xen_hvm_domain()) { |
357 | uint64_t v = 0; |
358 | int err; |
359 | |
360 | err = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, value: &v); |
361 | if (!err && v) |
362 | info->evtchn = v; |
363 | } else |
364 | info->evtchn = xen_start_info->console.domU.evtchn; |
365 | } |
366 | |
367 | void xen_console_resume(void) |
368 | { |
369 | struct xencons_info *info = vtermno_to_xencons(HVC_COOKIE); |
370 | if (info != NULL && info->irq) { |
371 | if (!xen_initial_domain()) |
372 | xen_console_update_evtchn(info); |
373 | rebind_evtchn_irq(evtchn: info->evtchn, irq: info->irq); |
374 | } |
375 | } |
376 | |
377 | #ifdef CONFIG_HVC_XEN_FRONTEND |
378 | static void xencons_disconnect_backend(struct xencons_info *info) |
379 | { |
380 | if (info->hvc != NULL) |
381 | hvc_remove(hp: info->hvc); |
382 | info->hvc = NULL; |
383 | if (info->irq > 0) { |
384 | evtchn_put(evtchn: info->evtchn); |
385 | info->irq = 0; |
386 | info->evtchn = 0; |
387 | } |
388 | /* evtchn_put() will also close it so this is only an error path */ |
389 | if (info->evtchn > 0) |
390 | xenbus_free_evtchn(dev: info->xbdev, port: info->evtchn); |
391 | info->evtchn = 0; |
392 | if (info->gntref > 0) |
393 | gnttab_free_grant_references(head: info->gntref); |
394 | info->gntref = 0; |
395 | } |
396 | |
397 | static void xencons_free(struct xencons_info *info) |
398 | { |
399 | free_page((unsigned long)info->intf); |
400 | info->intf = NULL; |
401 | info->vtermno = 0; |
402 | kfree(objp: info); |
403 | } |
404 | |
405 | static int xen_console_remove(struct xencons_info *info) |
406 | { |
407 | unsigned long flags; |
408 | |
409 | xencons_disconnect_backend(info); |
410 | spin_lock_irqsave(&xencons_lock, flags); |
411 | list_del(entry: &info->list); |
412 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
413 | if (info->xbdev != NULL) |
414 | xencons_free(info); |
415 | else { |
416 | if (xen_hvm_domain()) |
417 | iounmap(addr: info->intf); |
418 | kfree(objp: info); |
419 | } |
420 | return 0; |
421 | } |
422 | |
423 | static void xencons_remove(struct xenbus_device *dev) |
424 | { |
425 | xen_console_remove(info: dev_get_drvdata(dev: &dev->dev)); |
426 | } |
427 | |
428 | static int xencons_connect_backend(struct xenbus_device *dev, |
429 | struct xencons_info *info) |
430 | { |
431 | int ret, evtchn, devid, ref, irq; |
432 | struct xenbus_transaction xbt; |
433 | grant_ref_t gref_head; |
434 | |
435 | ret = xenbus_alloc_evtchn(dev, port: &evtchn); |
436 | if (ret) |
437 | return ret; |
438 | info->evtchn = evtchn; |
439 | irq = bind_evtchn_to_irq_lateeoi(evtchn); |
440 | if (irq < 0) |
441 | return irq; |
442 | info->irq = irq; |
443 | devid = dev->nodename[strlen(dev->nodename) - 1] - '0'; |
444 | info->hvc = hvc_alloc(vtermno: xenbus_devid_to_vtermno(devid), |
445 | data: irq, ops: &domU_hvc_ops, outbuf_size: 256); |
446 | if (IS_ERR(ptr: info->hvc)) |
447 | return PTR_ERR(ptr: info->hvc); |
448 | ret = gnttab_alloc_grant_references(count: 1, pprivate_head: &gref_head); |
449 | if (ret < 0) |
450 | return ret; |
451 | info->gntref = gref_head; |
452 | ref = gnttab_claim_grant_reference(pprivate_head: &gref_head); |
453 | if (ref < 0) |
454 | return ref; |
455 | gnttab_grant_foreign_access_ref(ref, domid: info->xbdev->otherend_id, |
456 | virt_to_gfn(info->intf), readonly: 0); |
457 | |
458 | again: |
459 | ret = xenbus_transaction_start(t: &xbt); |
460 | if (ret) { |
461 | xenbus_dev_fatal(dev, err: ret, fmt: "starting transaction" ); |
462 | return ret; |
463 | } |
464 | ret = xenbus_printf(t: xbt, dir: dev->nodename, node: "ring-ref" , fmt: "%d" , ref); |
465 | if (ret) |
466 | goto error_xenbus; |
467 | ret = xenbus_printf(t: xbt, dir: dev->nodename, node: "port" , fmt: "%u" , |
468 | evtchn); |
469 | if (ret) |
470 | goto error_xenbus; |
471 | ret = xenbus_transaction_end(t: xbt, abort: 0); |
472 | if (ret) { |
473 | if (ret == -EAGAIN) |
474 | goto again; |
475 | xenbus_dev_fatal(dev, err: ret, fmt: "completing transaction" ); |
476 | return ret; |
477 | } |
478 | |
479 | xenbus_switch_state(dev, new_state: XenbusStateInitialised); |
480 | return 0; |
481 | |
482 | error_xenbus: |
483 | xenbus_transaction_end(t: xbt, abort: 1); |
484 | xenbus_dev_fatal(dev, err: ret, fmt: "writing xenstore" ); |
485 | return ret; |
486 | } |
487 | |
488 | static int xencons_probe(struct xenbus_device *dev, |
489 | const struct xenbus_device_id *id) |
490 | { |
491 | int ret, devid; |
492 | struct xencons_info *info; |
493 | unsigned long flags; |
494 | |
495 | devid = dev->nodename[strlen(dev->nodename) - 1] - '0'; |
496 | if (devid == 0) |
497 | return -ENODEV; |
498 | |
499 | info = kzalloc(size: sizeof(struct xencons_info), GFP_KERNEL); |
500 | if (!info) |
501 | return -ENOMEM; |
502 | spin_lock_init(&info->ring_lock); |
503 | dev_set_drvdata(dev: &dev->dev, data: info); |
504 | info->xbdev = dev; |
505 | info->vtermno = xenbus_devid_to_vtermno(devid); |
506 | info->intf = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); |
507 | if (!info->intf) |
508 | goto error_nomem; |
509 | |
510 | ret = xencons_connect_backend(dev, info); |
511 | if (ret < 0) |
512 | goto error; |
513 | spin_lock_irqsave(&xencons_lock, flags); |
514 | list_add_tail(new: &info->list, head: &xenconsoles); |
515 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
516 | |
517 | return 0; |
518 | |
519 | error_nomem: |
520 | ret = -ENOMEM; |
521 | xenbus_dev_fatal(dev, err: ret, fmt: "allocating device memory" ); |
522 | error: |
523 | xencons_disconnect_backend(info); |
524 | xencons_free(info); |
525 | return ret; |
526 | } |
527 | |
528 | static int xencons_resume(struct xenbus_device *dev) |
529 | { |
530 | struct xencons_info *info = dev_get_drvdata(dev: &dev->dev); |
531 | |
532 | xencons_disconnect_backend(info); |
533 | memset(info->intf, 0, XEN_PAGE_SIZE); |
534 | return xencons_connect_backend(dev, info); |
535 | } |
536 | |
537 | static void xencons_backend_changed(struct xenbus_device *dev, |
538 | enum xenbus_state backend_state) |
539 | { |
540 | switch (backend_state) { |
541 | case XenbusStateReconfiguring: |
542 | case XenbusStateReconfigured: |
543 | case XenbusStateInitialising: |
544 | case XenbusStateInitialised: |
545 | case XenbusStateUnknown: |
546 | break; |
547 | |
548 | case XenbusStateInitWait: |
549 | break; |
550 | |
551 | case XenbusStateConnected: |
552 | xenbus_switch_state(dev, new_state: XenbusStateConnected); |
553 | break; |
554 | |
555 | case XenbusStateClosed: |
556 | if (dev->state == XenbusStateClosed) |
557 | break; |
558 | fallthrough; /* Missed the backend's CLOSING state */ |
559 | case XenbusStateClosing: { |
560 | struct xencons_info *info = dev_get_drvdata(dev: &dev->dev);; |
561 | |
562 | /* |
563 | * Don't tear down the evtchn and grant ref before the other |
564 | * end has disconnected, but do stop userspace from trying |
565 | * to use the device before we allow the backend to close. |
566 | */ |
567 | if (info->hvc) { |
568 | hvc_remove(hp: info->hvc); |
569 | info->hvc = NULL; |
570 | } |
571 | |
572 | xenbus_frontend_closed(dev); |
573 | break; |
574 | } |
575 | } |
576 | } |
577 | |
578 | static const struct xenbus_device_id xencons_ids[] = { |
579 | { "console" }, |
580 | { "" } |
581 | }; |
582 | |
583 | static struct xenbus_driver xencons_driver = { |
584 | .name = "xenconsole" , |
585 | .ids = xencons_ids, |
586 | .probe = xencons_probe, |
587 | .remove = xencons_remove, |
588 | .resume = xencons_resume, |
589 | .otherend_changed = xencons_backend_changed, |
590 | .not_essential = true, |
591 | }; |
592 | #endif /* CONFIG_HVC_XEN_FRONTEND */ |
593 | |
594 | static int __init xen_hvc_init(void) |
595 | { |
596 | int r; |
597 | struct xencons_info *info; |
598 | const struct hv_ops *ops; |
599 | |
600 | if (!xen_domain()) |
601 | return -ENODEV; |
602 | |
603 | if (xen_initial_domain()) { |
604 | ops = &dom0_hvc_ops; |
605 | r = xen_initial_domain_console_init(); |
606 | if (r < 0) |
607 | goto register_fe; |
608 | info = vtermno_to_xencons(HVC_COOKIE); |
609 | } else { |
610 | ops = &domU_hvc_ops; |
611 | if (xen_hvm_domain()) |
612 | r = xen_hvm_console_init(); |
613 | else |
614 | r = xen_pv_console_init(); |
615 | if (r < 0) |
616 | goto register_fe; |
617 | |
618 | info = vtermno_to_xencons(HVC_COOKIE); |
619 | info->irq = bind_evtchn_to_irq_lateeoi(evtchn: info->evtchn); |
620 | } |
621 | if (info->irq < 0) |
622 | info->irq = 0; /* NO_IRQ */ |
623 | else |
624 | irq_set_noprobe(irq: info->irq); |
625 | |
626 | info->hvc = hvc_alloc(HVC_COOKIE, data: info->irq, ops, outbuf_size: 256); |
627 | if (IS_ERR(ptr: info->hvc)) { |
628 | unsigned long flags; |
629 | |
630 | r = PTR_ERR(ptr: info->hvc); |
631 | spin_lock_irqsave(&xencons_lock, flags); |
632 | list_del(entry: &info->list); |
633 | spin_unlock_irqrestore(lock: &xencons_lock, flags); |
634 | if (info->irq) |
635 | evtchn_put(evtchn: info->evtchn); |
636 | kfree(objp: info); |
637 | return r; |
638 | } |
639 | |
640 | r = 0; |
641 | register_fe: |
642 | #ifdef CONFIG_HVC_XEN_FRONTEND |
643 | r = xenbus_register_frontend(&xencons_driver); |
644 | #endif |
645 | return r; |
646 | } |
647 | device_initcall(xen_hvc_init); |
648 | |
649 | static int xen_cons_init(void) |
650 | { |
651 | const struct hv_ops *ops; |
652 | |
653 | if (!xen_domain()) |
654 | return 0; |
655 | |
656 | if (xen_initial_domain()) |
657 | ops = &dom0_hvc_ops; |
658 | else { |
659 | int r; |
660 | ops = &domU_hvc_ops; |
661 | |
662 | if (xen_hvm_domain()) |
663 | r = xen_hvm_console_init(); |
664 | else |
665 | r = xen_pv_console_init(); |
666 | if (r < 0) |
667 | return r; |
668 | } |
669 | |
670 | hvc_instantiate(HVC_COOKIE, index: 0, ops); |
671 | return 0; |
672 | } |
673 | console_initcall(xen_cons_init); |
674 | |
675 | #ifdef CONFIG_X86 |
676 | static void xen_hvm_early_write(uint32_t vtermno, const char *str, int len) |
677 | { |
678 | if (xen_cpuid_base()) |
679 | outsb(port: 0xe9, addr: str, count: len); |
680 | } |
681 | #else |
682 | static void xen_hvm_early_write(uint32_t vtermno, const char *str, int len) { } |
683 | #endif |
684 | |
685 | #ifdef CONFIG_EARLY_PRINTK |
686 | static int __init xenboot_console_setup(struct console *console, char *string) |
687 | { |
688 | static struct xencons_info xenboot; |
689 | |
690 | if (xen_initial_domain() || !xen_pv_domain()) |
691 | return 0; |
692 | |
693 | return xencons_info_pv_init(info: &xenboot, vtermno: 0); |
694 | } |
695 | |
696 | static void xenboot_write_console(struct console *console, const char *string, |
697 | unsigned len) |
698 | { |
699 | unsigned int linelen, off = 0; |
700 | const char *pos; |
701 | |
702 | if (dom0_write_console(vtermno: 0, str: string, len) >= 0) |
703 | return; |
704 | |
705 | if (!xen_pv_domain()) { |
706 | xen_hvm_early_write(vtermno: 0, str: string, len); |
707 | return; |
708 | } |
709 | |
710 | if (domU_write_console(vtermno: 0, data: "(early) " , len: 8) < 0) |
711 | return; |
712 | while (off < len && NULL != (pos = strchr(string+off, '\n'))) { |
713 | linelen = pos-string+off; |
714 | if (off + linelen > len) |
715 | break; |
716 | domU_write_console(vtermno: 0, data: string+off, len: linelen); |
717 | domU_write_console(vtermno: 0, data: "\r\n" , len: 2); |
718 | off += linelen + 1; |
719 | } |
720 | if (off < len) |
721 | domU_write_console(vtermno: 0, data: string+off, len: len-off); |
722 | } |
723 | |
724 | struct console xenboot_console = { |
725 | .name = "xenboot" , |
726 | .write = xenboot_write_console, |
727 | .setup = xenboot_console_setup, |
728 | .flags = CON_PRINTBUFFER | CON_BOOT | CON_ANYTIME, |
729 | .index = -1, |
730 | }; |
731 | #endif /* CONFIG_EARLY_PRINTK */ |
732 | |
733 | void xen_raw_console_write(const char *str) |
734 | { |
735 | ssize_t len = strlen(str); |
736 | int rc = 0; |
737 | |
738 | if (xen_domain()) { |
739 | rc = dom0_write_console(vtermno: 0, str, len); |
740 | if (rc != -ENOSYS || !xen_hvm_domain()) |
741 | return; |
742 | } |
743 | xen_hvm_early_write(vtermno: 0, str, len); |
744 | } |
745 | |
746 | void xen_raw_printk(const char *fmt, ...) |
747 | { |
748 | static char buf[512]; |
749 | va_list ap; |
750 | |
751 | va_start(ap, fmt); |
752 | vsnprintf(buf, size: sizeof(buf), fmt, args: ap); |
753 | va_end(ap); |
754 | |
755 | xen_raw_console_write(str: buf); |
756 | } |
757 | |
758 | static void xenboot_earlycon_write(struct console *console, |
759 | const char *string, |
760 | unsigned len) |
761 | { |
762 | dom0_write_console(vtermno: 0, str: string, len); |
763 | } |
764 | |
765 | static int __init xenboot_earlycon_setup(struct earlycon_device *device, |
766 | const char *opt) |
767 | { |
768 | device->con->write = xenboot_earlycon_write; |
769 | return 0; |
770 | } |
771 | EARLYCON_DECLARE(xenboot, xenboot_earlycon_setup); |
772 | |