Skip to main content

starnix_modules_hvdcp_opti/
device.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::iio_file::{
6    build_battery_power_supply_directory, build_bms_power_supply_directory, build_iio0_directory,
7    build_iio3_directory, build_usb_power_supply_directory,
8};
9use super::qbg_battery_file::create_battery_profile_device;
10use super::qbg_file::create_qbg_device;
11use super::utils::ReadWriteBytesFile;
12use fidl_fuchsia_hardware_qcom_hvdcpopti as fhvdcpopti;
13use starnix_core::device::DeviceMode;
14use starnix_core::device::kobject::{Class, DeviceMetadata};
15use starnix_core::device::service_watcher::{ServiceBindingScope, ServiceWatcher};
16use starnix_core::fs::sysfs::build_device_directory;
17use starnix_core::task::{CurrentTask, Kernel};
18use starnix_core::vfs::NamespaceNode;
19use starnix_logging::log_warn;
20use starnix_uapi::device_id::DeviceId;
21use starnix_uapi::errno;
22use starnix_uapi::errors::Errno;
23use starnix_uapi::file_mode::mode;
24use starnix_uapi::open_flags::OpenFlags;
25use std::sync::Arc;
26
27pub fn hvdcp_opti_init(kernel: &Kernel) {
28    let qbg_class = kernel.device_registry.objects.class_with_dir("qbg".into(), |dir| {
29        dir.entry("qbg_context", ReadWriteBytesFile::new_node(), mode!(IFREG, 0o666));
30    });
31    ServiceWatcher::register_singleton(
32        kernel,
33        fhvdcpopti::ServiceMarker,
34        move |current_task, service_proxy, scope| {
35            if let Err(e) =
36                register_hvdcp_opti_devices(current_task, &qbg_class, service_proxy, scope)
37            {
38                log_warn!("Failed to initialize hvdcp_opti devices: {e:?}");
39            }
40        },
41    );
42}
43
44fn register_hvdcp_opti_devices(
45    current_task: &CurrentTask,
46    qbg_class: &Class,
47    service_proxy: Arc<fhvdcpopti::ServiceProxy>,
48    scope: &mut ServiceBindingScope,
49) -> Result<(), Errno> {
50    let proxy = Arc::new(service_proxy.connect_to_iio_sync().map_err(|e| {
51        log_warn!("Could not connect to hvdcp_opti IIO protocol: {e}");
52        errno!(EINVAL)
53    })?);
54
55    let kernel = current_task.kernel();
56    let registry = &kernel.device_registry;
57    let objects = &registry.objects;
58    let qbg_battery_class = objects.get_or_create_class("qbg_battery".into());
59    let soc = objects.soc_device();
60    let platform_bus = objects.platform_bus();
61    let spmi_bus = objects.get_or_create_bus("spmi".into());
62    let iio = objects.get_or_create_bus("iio".into());
63    let power_supply = objects.power_supply_class();
64
65    let spmi = registry.add_bus_device(
66        "1c40000.qcom,spmi".into(),
67        Some(soc),
68        platform_bus.clone(),
69        build_device_directory,
70    );
71    let spmi_0 = registry.add_bus_device(
72        "spmi-0".into(),
73        Some(spmi.clone()),
74        spmi_bus.clone(),
75        build_device_directory,
76    );
77    let spmi_0_00 = registry.add_bus_device(
78        "0-00".into(),
79        Some(spmi_0.clone()),
80        spmi_bus,
81        build_device_directory,
82    );
83    let smblite = registry.add_bus_device(
84        "1c40000.qcom,spmi:qcom,pm5100@0:qcom,qpnp-smblite".into(),
85        Some(spmi_0_00.clone()),
86        platform_bus.clone(),
87        build_device_directory,
88    );
89    let qbg = registry.add_bus_device(
90        "1c40000.qcom,spmi:qcom,pm5100@0:qpnp,qbg@4f00".into(),
91        Some(spmi_0_00.clone()),
92        platform_bus,
93        build_device_directory,
94    );
95
96    // /sys/bus/iio/devices/iio:device{0,3}
97    let iio0 = registry.add_bus_device(
98        "iio:device0".into(),
99        Some(smblite.clone()),
100        iio.clone(),
101        |device, dir| build_iio0_directory(device, &proxy, dir),
102    );
103
104    let iio3 =
105        registry.add_bus_device("iio:device3".into(), Some(qbg.clone()), iio, |device, dir| {
106            build_iio3_directory(device, &proxy, dir)
107        });
108
109    // /sys/class/power_supply/usb
110    let usb = registry.add_numberless_device(
111        "usb".into(),
112        Some(smblite.clone()),
113        power_supply.clone(),
114        |device, dir| build_usb_power_supply_directory(device, &proxy, dir),
115    );
116
117    // /sys/class/power_supply/battery
118    let battery = registry.add_numberless_device(
119        "battery".into(),
120        Some(smblite.clone()),
121        power_supply.clone(),
122        |device, dir| build_battery_power_supply_directory(device, &proxy, dir),
123    );
124
125    // /sys/class/power_supply/bms
126    let bms = registry.add_numberless_device(
127        "bms".into(),
128        Some(qbg.clone()),
129        power_supply,
130        build_bms_power_supply_directory,
131    );
132
133    let sysfs_devices = [spmi, spmi_0, spmi_0_00, smblite, qbg, iio0, iio3, usb, battery, bms];
134
135    // Register /dev/qbg and /dev/qbg_battery after the bus, IIO, and power_supply sysfs nodes
136    // exist, because the userspace hvdcp_opti daemon waits on /dev/qbg and immediately accesses
137    // those sysfs paths.
138    // /dev/qbg
139    let qbg_service = service_proxy.clone();
140    let qbg_dev = registry.add_device(
141        kernel,
142        "qbg".into(),
143        DeviceMetadata::new("qbg".into(), DeviceId::new(484, 0), DeviceMode::Char),
144        qbg_class.clone(),
145        move |_current_task: &CurrentTask,
146              _id: DeviceId,
147              _node: &NamespaceNode,
148              _flags: OpenFlags| create_qbg_device(&qbg_service),
149    );
150
151    // /dev/qbg_battery
152    let battery_service = service_proxy;
153    let qbg_battery_dev = registry.add_device(
154        kernel,
155        "qbg_battery".into(),
156        DeviceMetadata::new("qbg_battery".into(), DeviceId::new(485, 0), DeviceMode::Char),
157        qbg_battery_class,
158        move |_current_task: &CurrentTask,
159              _id: DeviceId,
160              _node: &NamespaceNode,
161              _flags: OpenFlags| create_battery_profile_device(&battery_service),
162    );
163
164    for dev in sysfs_devices {
165        scope.add_scoped_device(dev);
166    }
167    scope.add_scoped_device(qbg_dev);
168    scope.add_scoped_device(qbg_battery_dev);
169    scope.monitor_channel(proxy);
170    Ok(())
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use fidl::endpoints::{MemberOpener, ServiceProxy as _};
177    use starnix_core::device::kobject::{Device, UEventAction, UEventContext};
178    use starnix_core::testing::spawn_kernel_and_run;
179    use starnix_sync::Mutex;
180
181    struct NoopMemberOpener;
182
183    impl MemberOpener for NoopMemberOpener {
184        fn open_member(&self, _member: &str, _server_end: zx::Channel) -> Result<(), fidl::Error> {
185            Ok(())
186        }
187
188        fn instance_name(&self) -> &str {
189            "default"
190        }
191    }
192
193    #[::fuchsia::test]
194    async fn register_hvdcp_opti_devices_registers_sysfs_before_dev_nodes() {
195        spawn_kernel_and_run(async |current_task| {
196            let kernel = current_task.kernel();
197            let qbg_class = kernel.device_registry.objects.class_with_dir("qbg".into(), |dir| {
198                dir.entry("qbg_context", ReadWriteBytesFile::new_node(), mode!(IFREG, 0o666));
199            });
200            let events = Arc::new(Mutex::new(Vec::new()));
201            let listener_events = events.clone();
202            let key = kernel.device_registry.register_listener(
203                move |action: UEventAction, device: Device, _context: UEventContext| {
204                    listener_events.lock().push((action, device.path_from_depth(0)));
205                },
206            );
207
208            let service_proxy =
209                Arc::new(fhvdcpopti::ServiceProxy::from_member_opener(Box::new(NoopMemberOpener)));
210            let mut scope = ServiceBindingScope::default();
211            register_hvdcp_opti_devices(current_task, &qbg_class, service_proxy, &mut scope)
212                .expect("register_hvdcp_opti_devices");
213
214            kernel.device_registry.unregister_listener(&key);
215
216            assert_eq!(
217                std::mem::take(&mut *events.lock()),
218                vec![
219                    (UEventAction::Add, "devices/platform/soc/1c40000.qcom,spmi".into()),
220                    (UEventAction::Add, "devices/platform/soc/1c40000.qcom,spmi/spmi-0".into()),
221                    (
222                        UEventAction::Add,
223                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00".into(),
224                    ),
225                    (
226                        UEventAction::Add,
227                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qcom,qpnp-smblite".into(),
228                    ),
229                    (
230                        UEventAction::Add,
231                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qpnp,qbg@4f00".into(),
232                    ),
233                    (
234                        UEventAction::Add,
235                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qcom,qpnp-smblite/iio:device0".into(),
236                    ),
237                    (
238                        UEventAction::Add,
239                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qpnp,qbg@4f00/iio:device3".into(),
240                    ),
241                    (
242                        UEventAction::Add,
243                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qcom,qpnp-smblite/power_supply/usb".into(),
244                    ),
245                    (
246                        UEventAction::Add,
247                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qcom,qpnp-smblite/power_supply/battery".into(),
248                    ),
249                    (
250                        UEventAction::Add,
251                        "devices/platform/soc/1c40000.qcom,spmi/spmi-0/0-00/1c40000.qcom,spmi:qcom,pm5100@0:qpnp,qbg@4f00/power_supply/bms".into(),
252                    ),
253                    (UEventAction::Add, "devices/virtual/qbg/qbg".into()),
254                    (UEventAction::Add, "devices/virtual/qbg_battery/qbg_battery".into()),
255                ]
256            );
257        })
258        .await;
259    }
260}