xref: /qemu/rust/hw/char/pl011/src/device.rs (revision 8c80c472da6342c5924bc4ea7e87c77ca61477b8)
1 // Copyright 2024, Linaro Limited
2 // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
3 // SPDX-License-Identifier: GPL-2.0-or-later
4 
5 use core::ptr::{addr_of_mut, NonNull};
6 use std::{
7     ffi::CStr,
8     os::raw::{c_int, c_uchar, c_uint, c_void},
9 };
10 
11 use qemu_api::{
12     bindings::{self, *},
13     c_str,
14     definitions::ObjectImpl,
15     device_class::{DeviceImpl, TYPE_SYS_BUS_DEVICE},
16     impl_device_class,
17     irq::InterruptSource,
18 };
19 
20 use crate::{
21     device_class,
22     memory_ops::PL011_OPS,
23     registers::{self, Interrupt},
24     RegisterOffset,
25 };
26 
27 /// Integer Baud Rate Divider, `UARTIBRD`
28 const IBRD_MASK: u32 = 0xffff;
29 
30 /// Fractional Baud Rate Divider, `UARTFBRD`
31 const FBRD_MASK: u32 = 0x3f;
32 
33 const DATA_BREAK: u32 = 1 << 10;
34 
35 /// QEMU sourced constant.
36 pub const PL011_FIFO_DEPTH: usize = 16_usize;
37 
38 #[derive(Clone, Copy, Debug)]
39 enum DeviceId {
40     #[allow(dead_code)]
41     Arm = 0,
42     Luminary,
43 }
44 
45 impl std::ops::Index<hwaddr> for DeviceId {
46     type Output = c_uchar;
47 
48     fn index(&self, idx: hwaddr) -> &Self::Output {
49         match self {
50             Self::Arm => &Self::PL011_ID_ARM[idx as usize],
51             Self::Luminary => &Self::PL011_ID_LUMINARY[idx as usize],
52         }
53     }
54 }
55 
56 impl DeviceId {
57     const PL011_ID_ARM: [c_uchar; 8] = [0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1];
58     const PL011_ID_LUMINARY: [c_uchar; 8] = [0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1];
59 }
60 
61 #[repr(C)]
62 #[derive(Debug, qemu_api_macros::Object, qemu_api_macros::offsets)]
63 /// PL011 Device Model in QEMU
64 pub struct PL011State {
65     pub parent_obj: SysBusDevice,
66     pub iomem: MemoryRegion,
67     #[doc(alias = "fr")]
68     pub flags: registers::Flags,
69     #[doc(alias = "lcr")]
70     pub line_control: registers::LineControl,
71     #[doc(alias = "rsr")]
72     pub receive_status_error_clear: registers::ReceiveStatusErrorClear,
73     #[doc(alias = "cr")]
74     pub control: registers::Control,
75     pub dmacr: u32,
76     pub int_enabled: u32,
77     pub int_level: u32,
78     pub read_fifo: [u32; PL011_FIFO_DEPTH],
79     pub ilpr: u32,
80     pub ibrd: u32,
81     pub fbrd: u32,
82     pub ifl: u32,
83     pub read_pos: usize,
84     pub read_count: usize,
85     pub read_trigger: usize,
86     #[doc(alias = "chr")]
87     pub char_backend: CharBackend,
88     /// QEMU interrupts
89     ///
90     /// ```text
91     ///  * sysbus MMIO region 0: device registers
92     ///  * sysbus IRQ 0: `UARTINTR` (combined interrupt line)
93     ///  * sysbus IRQ 1: `UARTRXINTR` (receive FIFO interrupt line)
94     ///  * sysbus IRQ 2: `UARTTXINTR` (transmit FIFO interrupt line)
95     ///  * sysbus IRQ 3: `UARTRTINTR` (receive timeout interrupt line)
96     ///  * sysbus IRQ 4: `UARTMSINTR` (momem status interrupt line)
97     ///  * sysbus IRQ 5: `UARTEINTR` (error interrupt line)
98     /// ```
99     #[doc(alias = "irq")]
100     pub interrupts: [InterruptSource; IRQMASK.len()],
101     #[doc(alias = "clk")]
102     pub clock: NonNull<Clock>,
103     #[doc(alias = "migrate_clk")]
104     pub migrate_clock: bool,
105     /// The byte string that identifies the device.
106     device_id: DeviceId,
107 }
108 
109 impl ObjectImpl for PL011State {
110     type Class = PL011Class;
111     const TYPE_NAME: &'static CStr = crate::TYPE_PL011;
112     const PARENT_TYPE_NAME: Option<&'static CStr> = Some(TYPE_SYS_BUS_DEVICE);
113     const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_init);
114 }
115 
116 #[repr(C)]
117 pub struct PL011Class {
118     _inner: [u8; 0],
119 }
120 
121 impl DeviceImpl for PL011State {
122     fn properties() -> &'static [Property] {
123         &device_class::PL011_PROPERTIES
124     }
125     fn vmsd() -> Option<&'static VMStateDescription> {
126         Some(&device_class::VMSTATE_PL011)
127     }
128     const REALIZE: Option<unsafe extern "C" fn(*mut DeviceState, *mut *mut Error)> =
129         Some(device_class::pl011_realize);
130     const RESET: Option<unsafe extern "C" fn(*mut DeviceState)> = Some(device_class::pl011_reset);
131 }
132 
133 impl_device_class!(PL011State);
134 
135 impl PL011State {
136     /// Initializes a pre-allocated, unitialized instance of `PL011State`.
137     ///
138     /// # Safety
139     ///
140     /// `self` must point to a correctly sized and aligned location for the
141     /// `PL011State` type. It must not be called more than once on the same
142     /// location/instance. All its fields are expected to hold unitialized
143     /// values with the sole exception of `parent_obj`.
144     unsafe fn init(&mut self) {
145         const CLK_NAME: &CStr = c_str!("clk");
146 
147         let sbd = unsafe { &mut *(addr_of_mut!(*self).cast::<SysBusDevice>()) };
148 
149         // SAFETY:
150         //
151         // self and self.iomem are guaranteed to be valid at this point since callers
152         // must make sure the `self` reference is valid.
153         unsafe {
154             memory_region_init_io(
155                 addr_of_mut!(self.iomem),
156                 addr_of_mut!(*self).cast::<Object>(),
157                 &PL011_OPS,
158                 addr_of_mut!(*self).cast::<c_void>(),
159                 Self::TYPE_NAME.as_ptr(),
160                 0x1000,
161             );
162             sysbus_init_mmio(sbd, addr_of_mut!(self.iomem));
163         }
164 
165         for irq in self.interrupts.iter() {
166             sbd.init_irq(irq);
167         }
168 
169         let dev = addr_of_mut!(*self).cast::<DeviceState>();
170 
171         // SAFETY:
172         //
173         // self.clock is not initialized at this point; but since `NonNull<_>` is Copy,
174         // we can overwrite the undefined value without side effects. This is
175         // safe since all PL011State instances are created by QOM code which
176         // calls this function to initialize the fields; therefore no code is
177         // able to access an invalid self.clock value.
178         unsafe {
179             self.clock = NonNull::new(qdev_init_clock_in(
180                 dev,
181                 CLK_NAME.as_ptr(),
182                 None, /* pl011_clock_update */
183                 addr_of_mut!(*self).cast::<c_void>(),
184                 ClockEvent::ClockUpdate.0,
185             ))
186             .unwrap();
187         }
188     }
189 
190     pub fn read(&mut self, offset: hwaddr, _size: c_uint) -> std::ops::ControlFlow<u64, u64> {
191         use RegisterOffset::*;
192 
193         std::ops::ControlFlow::Break(match RegisterOffset::try_from(offset) {
194             Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => {
195                 u64::from(self.device_id[(offset - 0xfe0) >> 2])
196             }
197             Err(_) => {
198                 // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset);
199                 0
200             }
201             Ok(DR) => {
202                 self.flags.set_receive_fifo_full(false);
203                 let c = self.read_fifo[self.read_pos];
204                 if self.read_count > 0 {
205                     self.read_count -= 1;
206                     self.read_pos = (self.read_pos + 1) & (self.fifo_depth() - 1);
207                 }
208                 if self.read_count == 0 {
209                     self.flags.set_receive_fifo_empty(true);
210                 }
211                 if self.read_count + 1 == self.read_trigger {
212                     self.int_level &= !registers::INT_RX;
213                 }
214                 // Update error bits.
215                 self.receive_status_error_clear = c.to_be_bytes()[3].into();
216                 self.update();
217                 // Must call qemu_chr_fe_accept_input, so return Continue:
218                 return std::ops::ControlFlow::Continue(c.into());
219             }
220             Ok(RSR) => u8::from(self.receive_status_error_clear).into(),
221             Ok(FR) => u16::from(self.flags).into(),
222             Ok(FBRD) => self.fbrd.into(),
223             Ok(ILPR) => self.ilpr.into(),
224             Ok(IBRD) => self.ibrd.into(),
225             Ok(LCR_H) => u16::from(self.line_control).into(),
226             Ok(CR) => {
227                 // We exercise our self-control.
228                 u16::from(self.control).into()
229             }
230             Ok(FLS) => self.ifl.into(),
231             Ok(IMSC) => self.int_enabled.into(),
232             Ok(RIS) => self.int_level.into(),
233             Ok(MIS) => u64::from(self.int_level & self.int_enabled),
234             Ok(ICR) => {
235                 // "The UARTICR Register is the interrupt clear register and is write-only"
236                 // Source: ARM DDI 0183G 3.3.13 Interrupt Clear Register, UARTICR
237                 0
238             }
239             Ok(DMACR) => self.dmacr.into(),
240         })
241     }
242 
243     pub fn write(&mut self, offset: hwaddr, value: u64) {
244         // eprintln!("write offset {offset} value {value}");
245         use RegisterOffset::*;
246         let value: u32 = value as u32;
247         match RegisterOffset::try_from(offset) {
248             Err(_bad_offset) => {
249                 eprintln!("write bad offset {offset} value {value}");
250             }
251             Ok(DR) => {
252                 // ??? Check if transmitter is enabled.
253                 let ch: u8 = value as u8;
254                 // XXX this blocks entire thread. Rewrite to use
255                 // qemu_chr_fe_write and background I/O callbacks
256 
257                 // SAFETY: self.char_backend is a valid CharBackend instance after it's been
258                 // initialized in realize().
259                 unsafe {
260                     qemu_chr_fe_write_all(addr_of_mut!(self.char_backend), &ch, 1);
261                 }
262                 self.loopback_tx(value);
263                 self.int_level |= registers::INT_TX;
264                 self.update();
265             }
266             Ok(RSR) => {
267                 self.receive_status_error_clear = 0.into();
268             }
269             Ok(FR) => {
270                 // flag writes are ignored
271             }
272             Ok(ILPR) => {
273                 self.ilpr = value;
274             }
275             Ok(IBRD) => {
276                 self.ibrd = value;
277             }
278             Ok(FBRD) => {
279                 self.fbrd = value;
280             }
281             Ok(LCR_H) => {
282                 let value = value as u16;
283                 let new_val: registers::LineControl = value.into();
284                 // Reset the FIFO state on FIFO enable or disable
285                 if bool::from(self.line_control.fifos_enabled())
286                     ^ bool::from(new_val.fifos_enabled())
287                 {
288                     self.reset_fifo();
289                 }
290                 if self.line_control.send_break() ^ new_val.send_break() {
291                     let mut break_enable: c_int = new_val.send_break().into();
292                     // SAFETY: self.char_backend is a valid CharBackend instance after it's been
293                     // initialized in realize().
294                     unsafe {
295                         qemu_chr_fe_ioctl(
296                             addr_of_mut!(self.char_backend),
297                             CHR_IOCTL_SERIAL_SET_BREAK as i32,
298                             addr_of_mut!(break_enable).cast::<c_void>(),
299                         );
300                     }
301                     self.loopback_break(break_enable > 0);
302                 }
303                 self.line_control = new_val;
304                 self.set_read_trigger();
305             }
306             Ok(CR) => {
307                 // ??? Need to implement the enable bit.
308                 let value = value as u16;
309                 self.control = value.into();
310                 self.loopback_mdmctrl();
311             }
312             Ok(FLS) => {
313                 self.ifl = value;
314                 self.set_read_trigger();
315             }
316             Ok(IMSC) => {
317                 self.int_enabled = value;
318                 self.update();
319             }
320             Ok(RIS) => {}
321             Ok(MIS) => {}
322             Ok(ICR) => {
323                 self.int_level &= !value;
324                 self.update();
325             }
326             Ok(DMACR) => {
327                 self.dmacr = value;
328                 if value & 3 > 0 {
329                     // qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
330                     eprintln!("pl011: DMA not implemented");
331                 }
332             }
333         }
334     }
335 
336     #[inline]
337     fn loopback_tx(&mut self, value: u32) {
338         if !self.loopback_enabled() {
339             return;
340         }
341 
342         // Caveat:
343         //
344         // In real hardware, TX loopback happens at the serial-bit level
345         // and then reassembled by the RX logics back into bytes and placed
346         // into the RX fifo. That is, loopback happens after TX fifo.
347         //
348         // Because the real hardware TX fifo is time-drained at the frame
349         // rate governed by the configured serial format, some loopback
350         // bytes in TX fifo may still be able to get into the RX fifo
351         // that could be full at times while being drained at software
352         // pace.
353         //
354         // In such scenario, the RX draining pace is the major factor
355         // deciding which loopback bytes get into the RX fifo, unless
356         // hardware flow-control is enabled.
357         //
358         // For simplicity, the above described is not emulated.
359         self.put_fifo(value);
360     }
361 
362     fn loopback_mdmctrl(&mut self) {
363         if !self.loopback_enabled() {
364             return;
365         }
366 
367         /*
368          * Loopback software-driven modem control outputs to modem status inputs:
369          *   FR.RI  <= CR.Out2
370          *   FR.DCD <= CR.Out1
371          *   FR.CTS <= CR.RTS
372          *   FR.DSR <= CR.DTR
373          *
374          * The loopback happens immediately even if this call is triggered
375          * by setting only CR.LBE.
376          *
377          * CTS/RTS updates due to enabled hardware flow controls are not
378          * dealt with here.
379          */
380 
381         self.flags.set_ring_indicator(self.control.out_2());
382         self.flags.set_data_carrier_detect(self.control.out_1());
383         self.flags.set_clear_to_send(self.control.request_to_send());
384         self.flags
385             .set_data_set_ready(self.control.data_transmit_ready());
386 
387         // Change interrupts based on updated FR
388         let mut il = self.int_level;
389 
390         il &= !Interrupt::MS;
391 
392         if self.flags.data_set_ready() {
393             il |= Interrupt::DSR as u32;
394         }
395         if self.flags.data_carrier_detect() {
396             il |= Interrupt::DCD as u32;
397         }
398         if self.flags.clear_to_send() {
399             il |= Interrupt::CTS as u32;
400         }
401         if self.flags.ring_indicator() {
402             il |= Interrupt::RI as u32;
403         }
404         self.int_level = il;
405         self.update();
406     }
407 
408     fn loopback_break(&mut self, enable: bool) {
409         if enable {
410             self.loopback_tx(DATA_BREAK);
411         }
412     }
413 
414     fn set_read_trigger(&mut self) {
415         self.read_trigger = 1;
416     }
417 
418     pub fn realize(&mut self) {
419         // SAFETY: self.char_backend has the correct size and alignment for a
420         // CharBackend object, and its callbacks are of the correct types.
421         unsafe {
422             qemu_chr_fe_set_handlers(
423                 addr_of_mut!(self.char_backend),
424                 Some(pl011_can_receive),
425                 Some(pl011_receive),
426                 Some(pl011_event),
427                 None,
428                 addr_of_mut!(*self).cast::<c_void>(),
429                 core::ptr::null_mut(),
430                 true,
431             );
432         }
433     }
434 
435     pub fn reset(&mut self) {
436         self.line_control.reset();
437         self.receive_status_error_clear.reset();
438         self.dmacr = 0;
439         self.int_enabled = 0;
440         self.int_level = 0;
441         self.ilpr = 0;
442         self.ibrd = 0;
443         self.fbrd = 0;
444         self.read_trigger = 1;
445         self.ifl = 0x12;
446         self.control.reset();
447         self.flags = 0.into();
448         self.reset_fifo();
449     }
450 
451     pub fn reset_fifo(&mut self) {
452         self.read_count = 0;
453         self.read_pos = 0;
454 
455         /* Reset FIFO flags */
456         self.flags.reset();
457     }
458 
459     pub fn can_receive(&self) -> bool {
460         // trace_pl011_can_receive(s->lcr, s->read_count, r);
461         self.read_count < self.fifo_depth()
462     }
463 
464     pub fn event(&mut self, event: QEMUChrEvent) {
465         if event == bindings::QEMUChrEvent::CHR_EVENT_BREAK && !self.fifo_enabled() {
466             self.put_fifo(DATA_BREAK);
467             self.receive_status_error_clear.set_break_error(true);
468         }
469     }
470 
471     #[inline]
472     pub fn fifo_enabled(&self) -> bool {
473         matches!(self.line_control.fifos_enabled(), registers::Mode::FIFO)
474     }
475 
476     #[inline]
477     pub fn loopback_enabled(&self) -> bool {
478         self.control.enable_loopback()
479     }
480 
481     #[inline]
482     pub fn fifo_depth(&self) -> usize {
483         // Note: FIFO depth is expected to be power-of-2
484         if self.fifo_enabled() {
485             return PL011_FIFO_DEPTH;
486         }
487         1
488     }
489 
490     pub fn put_fifo(&mut self, value: c_uint) {
491         let depth = self.fifo_depth();
492         assert!(depth > 0);
493         let slot = (self.read_pos + self.read_count) & (depth - 1);
494         self.read_fifo[slot] = value;
495         self.read_count += 1;
496         self.flags.set_receive_fifo_empty(false);
497         if self.read_count == depth {
498             self.flags.set_receive_fifo_full(true);
499         }
500 
501         if self.read_count == self.read_trigger {
502             self.int_level |= registers::INT_RX;
503             self.update();
504         }
505     }
506 
507     pub fn update(&self) {
508         let flags = self.int_level & self.int_enabled;
509         for (irq, i) in self.interrupts.iter().zip(IRQMASK) {
510             irq.set(flags & i != 0);
511         }
512     }
513 
514     pub fn post_load(&mut self, _version_id: u32) -> Result<(), ()> {
515         /* Sanity-check input state */
516         if self.read_pos >= self.read_fifo.len() || self.read_count > self.read_fifo.len() {
517             return Err(());
518         }
519 
520         if !self.fifo_enabled() && self.read_count > 0 && self.read_pos > 0 {
521             // Older versions of PL011 didn't ensure that the single
522             // character in the FIFO in FIFO-disabled mode is in
523             // element 0 of the array; convert to follow the current
524             // code's assumptions.
525             self.read_fifo[0] = self.read_fifo[self.read_pos];
526             self.read_pos = 0;
527         }
528 
529         self.ibrd &= IBRD_MASK;
530         self.fbrd &= FBRD_MASK;
531 
532         Ok(())
533     }
534 }
535 
536 /// Which bits in the interrupt status matter for each outbound IRQ line ?
537 pub const IRQMASK: [u32; 6] = [
538     /* combined IRQ */
539     Interrupt::E
540         | Interrupt::MS
541         | Interrupt::RT as u32
542         | Interrupt::TX as u32
543         | Interrupt::RX as u32,
544     Interrupt::RX as u32,
545     Interrupt::TX as u32,
546     Interrupt::RT as u32,
547     Interrupt::MS,
548     Interrupt::E,
549 ];
550 
551 /// # Safety
552 ///
553 /// We expect the FFI user of this function to pass a valid pointer, that has
554 /// the same size as [`PL011State`]. We also expect the device is
555 /// readable/writeable from one thread at any time.
556 pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int {
557     unsafe {
558         debug_assert!(!opaque.is_null());
559         let state = NonNull::new_unchecked(opaque.cast::<PL011State>());
560         state.as_ref().can_receive().into()
561     }
562 }
563 
564 /// # Safety
565 ///
566 /// We expect the FFI user of this function to pass a valid pointer, that has
567 /// the same size as [`PL011State`]. We also expect the device is
568 /// readable/writeable from one thread at any time.
569 ///
570 /// The buffer and size arguments must also be valid.
571 pub unsafe extern "C" fn pl011_receive(opaque: *mut c_void, buf: *const u8, size: c_int) {
572     unsafe {
573         debug_assert!(!opaque.is_null());
574         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
575         if state.as_ref().loopback_enabled() {
576             return;
577         }
578         if size > 0 {
579             debug_assert!(!buf.is_null());
580             state.as_mut().put_fifo(c_uint::from(buf.read_volatile()))
581         }
582     }
583 }
584 
585 /// # Safety
586 ///
587 /// We expect the FFI user of this function to pass a valid pointer, that has
588 /// the same size as [`PL011State`]. We also expect the device is
589 /// readable/writeable from one thread at any time.
590 pub unsafe extern "C" fn pl011_event(opaque: *mut c_void, event: QEMUChrEvent) {
591     unsafe {
592         debug_assert!(!opaque.is_null());
593         let mut state = NonNull::new_unchecked(opaque.cast::<PL011State>());
594         state.as_mut().event(event)
595     }
596 }
597 
598 /// # Safety
599 ///
600 /// We expect the FFI user of this function to pass a valid pointer for `chr`.
601 #[no_mangle]
602 pub unsafe extern "C" fn pl011_create(
603     addr: u64,
604     irq: qemu_irq,
605     chr: *mut Chardev,
606 ) -> *mut DeviceState {
607     unsafe {
608         let dev: *mut DeviceState = qdev_new(PL011State::TYPE_NAME.as_ptr());
609         let sysbus: *mut SysBusDevice = dev.cast::<SysBusDevice>();
610 
611         qdev_prop_set_chr(dev, c_str!("chardev").as_ptr(), chr);
612         sysbus_realize_and_unref(sysbus, addr_of_mut!(error_fatal));
613         sysbus_mmio_map(sysbus, 0, addr);
614         sysbus_connect_irq(sysbus, 0, irq);
615         dev
616     }
617 }
618 
619 /// # Safety
620 ///
621 /// We expect the FFI user of this function to pass a valid pointer, that has
622 /// the same size as [`PL011State`]. We also expect the device is
623 /// readable/writeable from one thread at any time.
624 pub unsafe extern "C" fn pl011_init(obj: *mut Object) {
625     unsafe {
626         debug_assert!(!obj.is_null());
627         let mut state = NonNull::new_unchecked(obj.cast::<PL011State>());
628         state.as_mut().init();
629     }
630 }
631 
632 #[repr(C)]
633 #[derive(Debug, qemu_api_macros::Object)]
634 /// PL011 Luminary device model.
635 pub struct PL011Luminary {
636     parent_obj: PL011State,
637 }
638 
639 #[repr(C)]
640 pub struct PL011LuminaryClass {
641     _inner: [u8; 0],
642 }
643 
644 /// Initializes a pre-allocated, unitialized instance of `PL011Luminary`.
645 ///
646 /// # Safety
647 ///
648 /// We expect the FFI user of this function to pass a valid pointer, that has
649 /// the same size as [`PL011Luminary`]. We also expect the device is
650 /// readable/writeable from one thread at any time.
651 pub unsafe extern "C" fn pl011_luminary_init(obj: *mut Object) {
652     unsafe {
653         debug_assert!(!obj.is_null());
654         let mut state = NonNull::new_unchecked(obj.cast::<PL011Luminary>());
655         let state = state.as_mut();
656         state.parent_obj.device_id = DeviceId::Luminary;
657     }
658 }
659 
660 impl ObjectImpl for PL011Luminary {
661     type Class = PL011LuminaryClass;
662     const TYPE_NAME: &'static CStr = crate::TYPE_PL011_LUMINARY;
663     const PARENT_TYPE_NAME: Option<&'static CStr> = Some(crate::TYPE_PL011);
664     const INSTANCE_INIT: Option<unsafe extern "C" fn(obj: *mut Object)> = Some(pl011_luminary_init);
665 }
666 
667 impl DeviceImpl for PL011Luminary {}
668 
669 impl_device_class!(PL011Luminary);
670