1#![doc = include_str!("../README.md")]
2
3use std::{
4 fmt,
5 marker::PhantomData,
6 ops::Deref,
7 os::unix::{
8 fs::{FileTypeExt, MetadataExt},
9 io::{AsRawFd, FromRawFd, RawFd},
10 },
11 pin::Pin,
12 sync::Arc,
13 task::{Context, Poll},
14};
15
16use gpiod_core::{invalid_input, major, minor, set_nonblock, Internal, Result};
17
18pub use gpiod_core::{
19 Active, AsValues, AsValuesMut, Bias, BitId, ChipInfo, Direction, DirectionType, Drive, Edge,
20 EdgeDetect, Event, Input, LineId, LineInfo, Masked, Options, Output, Values, ValuesInfo,
21 MAX_BITS, MAX_VALUES,
22};
23
24use async_io::Async;
25use async_std::{
26 fs,
27 fs::OpenOptions,
28 io::{Read, ReadExt},
29 os::unix::fs::OpenOptionsExt,
30 path::{Path, PathBuf},
31 stream::StreamExt,
32 task::spawn_blocking as asyncify,
33};
34
35#[doc(hidden)]
36pub struct File {
37 inner: Async<std::fs::File>,
39}
40
41impl File {
42 pub fn from_fd(fd: RawFd) -> Result<Self> {
43 let file = unsafe { std::fs::File::from_raw_fd(fd) };
44 Ok(Self {
45 inner: Async::new(file)?,
46 })
47 }
48
49 pub fn from_file(file: fs::File) -> Result<Self> {
50 let fd = file.as_raw_fd();
51 core::mem::forget(file);
52 Self::from_fd(fd)
53 }
54}
55
56impl AsRawFd for File {
57 fn as_raw_fd(&self) -> RawFd {
58 self.inner.as_raw_fd()
59 }
60}
61
62impl Read for File {
63 fn poll_read(
64 self: Pin<&mut Self>,
65 cx: &mut Context<'_>,
66 buf: &mut [u8],
67 ) -> Poll<Result<usize>> {
68 use std::io::Read;
69
70 match self.inner.poll_readable(cx) {
71 Poll::Ready(x) => x,
72 Poll::Pending => return Poll::Pending,
73 }?;
74
75 Poll::Ready(self.inner.get_ref().read(buf))
76 }
77}
78
79pub struct Lines<Direction> {
84 dir: PhantomData<Direction>,
85 info: Arc<Internal<ValuesInfo>>,
86 file: File,
88}
89
90impl<Direction> Deref for Lines<Direction> {
91 type Target = ValuesInfo;
92
93 fn deref(&self) -> &Self::Target {
94 &self.info
95 }
96}
97
98impl<Direction: DirectionType> Lines<Direction> {
99 pub async fn get_values<T: AsValuesMut + Send + 'static>(&self, mut values: T) -> Result<T> {
105 let fd = self.file.as_raw_fd();
106 let info = self.info.clone();
107 asyncify(move || info.get_values(fd, &mut values).map(|_| values)).await
108 }
109}
110
111impl Lines<Input> {
112 pub async fn read_event(&mut self) -> Result<Event> {
117 #[cfg(not(feature = "v2"))]
118 {
119 todo!();
120 }
121
122 #[cfg(feature = "v2")]
123 {
124 let mut event = gpiod_core::RawEvent::default();
125
126 gpiod_core::check_size(self.file.read(event.as_mut()).await?, &event)?;
127
128 event.as_event(self.info.index())
129 }
130 }
131}
132
133impl Lines<Output> {
134 pub async fn set_values<T: AsValues + Send + 'static>(&self, values: T) -> Result<()> {
139 let fd = self.file.as_raw_fd();
140 let info = self.info.clone();
141 asyncify(move || info.set_values(fd, values)).await
142 }
143}
144
145pub struct Chip {
150 info: Arc<Internal<ChipInfo>>,
151 file: File,
153}
154
155impl Deref for Chip {
156 type Target = ChipInfo;
157
158 fn deref(&self) -> &Self::Target {
159 &self.info
160 }
161}
162
163impl fmt::Display for Chip {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 self.info.fmt(f)
166 }
167}
168
169const O_NONBLOCK: i32 = 2048;
170
171impl Chip {
172 pub async fn new(path: impl AsRef<Path>) -> Result<Chip> {
174 let path = path.as_ref();
175
176 #[allow(unused_assignments)]
177 let mut full_path = None;
178
179 let path = if path.starts_with("/dev") {
180 path
181 } else {
182 full_path = Path::new("/dev").join(path).into();
183 full_path.as_ref().unwrap()
184 };
185
186 let file = File::from_file(
187 OpenOptions::new()
188 .read(true)
189 .write(true)
190 .custom_flags(O_NONBLOCK)
191 .open(path)
192 .await?,
193 )?;
194
195 Chip::check_device(path).await?;
196
197 let fd = file.as_raw_fd();
198 let info = Arc::new(asyncify(move || Internal::<ChipInfo>::from_fd(fd)).await?);
199
200 Ok(Chip { info, file })
201 }
202
203 pub async fn list_devices() -> Result<Vec<PathBuf>> {
205 let mut devices = Vec::new();
206 let mut dir = fs::read_dir("/dev").await?;
207
208 while let Some(ent) = dir.next().await {
209 let path = ent?.path();
210 if Self::check_device(&path).await.is_ok() {
211 devices.push(path);
212 }
213 }
214
215 Ok(devices)
216 }
217
218 async fn check_device(path: &Path) -> Result<()> {
219 let metadata = fs::symlink_metadata(&path).await?;
220
221 if !metadata.file_type().is_char_device() {
223 return Err(invalid_input("File is not character device"));
224 }
225
226 let rdev = metadata.rdev();
227
228 if fs::canonicalize(format!(
230 "/sys/dev/char/{}:{}/subsystem",
231 major(rdev),
232 minor(rdev)
233 ))
234 .await?
235 != Path::new("/sys/bus/gpio")
236 {
237 return Err(invalid_input("Character device is not a GPIO"));
238 }
239
240 Ok(())
241 }
242
243 pub async fn line_info(&self, line: LineId) -> Result<LineInfo> {
245 let fd = self.file.as_raw_fd();
246 let info = self.info.clone();
247 asyncify(move || info.line_info(fd, line)).await
248 }
249
250 pub async fn request_lines<Direction: DirectionType>(
255 &self,
256 options: Options<Direction, impl AsRef<[LineId]>, impl AsRef<str>>,
257 ) -> Result<Lines<Direction>> {
258 let fd = self.file.as_raw_fd();
259 let options = options.to_owned();
260 let info = self.info.clone();
261
262 let (info, fd) = asyncify(move || -> Result<_> {
263 let (info, fd) = info.request_lines(fd, options)?;
264 set_nonblock(fd)?;
265 Ok((info, fd))
266 })
267 .await?;
268
269 let file = File::from_fd(fd)?;
270 let info = Arc::new(info);
271
272 Ok(Lines {
273 dir: PhantomData,
274 info,
275 file,
276 })
277 }
278}