Skip to content

Commit 9dd5367

Browse files
Copilotkrwqpgrawehr
authored
Map errno values to human-readable messages in System.Device.Gpio error handling (#2436)
* Add errno to error message mapping using Marshal.GetLastPInvokeErrorMessage() Co-authored-by: krwq <660048+krwq@users.noreply.github.com> * Refactor to use centralized GetLastErrorMessage() helper to eliminate code duplication Co-authored-by: krwq <660048+krwq@users.noreply.github.com> * Add comments explaining why some error handling can't use centralized helper Co-authored-by: krwq <660048+krwq@users.noreply.github.com> * Improve comments to clarify GetLastWin32Error() can only be called once Co-authored-by: krwq <660048+krwq@users.noreply.github.com> * Fix another typo * Fixing the mess copilot left behind * Another markdown issue --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: krwq <660048+krwq@users.noreply.github.com> Co-authored-by: Patrick Grawehr <pgrawehr@hotmail.com>
1 parent 8a05f51 commit 9dd5367

File tree

12 files changed

+115
-45
lines changed

12 files changed

+115
-45
lines changed

src/System.Device.Gpio/Interop/Unix/libgpiod/V1/LineHandle.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public LineHandle(IntPtr handle)
1616
{
1717
if (handle == IntPtr.Zero)
1818
{
19-
throw ExceptionHelper.GetIOException(ExceptionResource.OpenPinError, Marshal.GetLastWin32Error());
19+
throw ExceptionHelper.GetIOException(ExceptionResource.OpenPinError, ExceptionHelper.GetLastErrorMessage());
2020
}
2121

2222
_handle = handle;

src/System.Device.Gpio/System/Device/Gpio/Drivers/LibGpiodDriver.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public LibGpiodDriver(int gpioChip = 0)
8181
_chip = new SafeChipHandle(LibgpiodV1.gpiod_chip_open_by_number(gpioChip));
8282
if (_chip == null || _chip.IsInvalid || _chip.IsClosed)
8383
{
84-
throw ExceptionHelper.GetIOException(ExceptionResource.NoChipFound, Marshal.GetLastWin32Error());
84+
throw ExceptionHelper.GetIOException(ExceptionResource.NoChipFound, ExceptionHelper.GetLastErrorMessage());
8585
}
8686

8787
_pinCount = LibgpiodV1.gpiod_chip_num_lines(_chip);
@@ -292,7 +292,7 @@ protected internal override PinValue Read(int pinNumber)
292292
int result = LibgpiodV1.gpiod_line_get_value(pinHandle.Handle);
293293
if (result == -1)
294294
{
295-
throw ExceptionHelper.GetIOException(ExceptionResource.ReadPinError, Marshal.GetLastWin32Error(), pinNumber);
295+
throw ExceptionHelper.GetIOException(ExceptionResource.ReadPinError, ExceptionHelper.GetLastErrorMessage(), pinNumber);
296296
}
297297

298298
_pinValue[pinNumber] = result;
@@ -354,7 +354,7 @@ protected internal override void SetPinMode(int pinNumber, PinMode mode)
354354

355355
if (requestResult == -1)
356356
{
357-
throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, Marshal.GetLastWin32Error(),
357+
throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, ExceptionHelper.GetLastErrorMessage(),
358358
pinNumber);
359359
}
360360

@@ -386,7 +386,7 @@ protected internal override void SetPinMode(int pinNumber, PinMode mode, PinValu
386386

387387
if (requestResult == -1)
388388
{
389-
throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, Marshal.GetLastWin32Error(),
389+
throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, ExceptionHelper.GetLastErrorMessage(),
390390
pinNumber);
391391
}
392392

src/System.Device.Gpio/System/Device/Gpio/Drivers/LibGpiodDriverEventHandler.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private void SubscribeForEvent(LineHandle pinHandle)
4040

4141
if (eventSuccess < 0)
4242
{
43-
throw ExceptionHelper.GetIOException(ExceptionResource.RequestEventError, Marshal.GetLastWin32Error(), _pinNumber);
43+
throw ExceptionHelper.GetIOException(ExceptionResource.RequestEventError, ExceptionHelper.GetLastErrorMessage(), _pinNumber);
4444
}
4545
}
4646

@@ -60,14 +60,19 @@ private Task InitializeEventDetectionTask(CancellationToken token, LineHandle pi
6060
WaitEventResult waitResult = LibgpiodV1.gpiod_line_event_wait(pinHandle.Handle, ref timeout);
6161
if (waitResult == WaitEventResult.Error)
6262
{
63+
// Can't use ExceptionHelper.GetLastErrorMessage() here because we need the error code
64+
// for the EINTR check. GetLastErrorMessage() would call GetLastWin32Error() internally,
65+
// and we can't call GetLastWin32Error() twice as subsequent calls might return different values.
6366
var errorCode = Marshal.GetLastWin32Error();
6467
if (errorCode == ERROR_CODE_EINTR)
6568
{
6669
// ignore Interrupted system call error and retry
6770
continue;
6871
}
6972

70-
throw ExceptionHelper.GetIOException(ExceptionResource.EventWaitError, errorCode, _pinNumber);
73+
string errorMessage = Marshal.GetLastPInvokeErrorMessage();
74+
string errorInfo = string.IsNullOrWhiteSpace(errorMessage) ? errorCode.ToString() : $"{errorCode} ({errorMessage})";
75+
throw ExceptionHelper.GetIOException(ExceptionResource.EventWaitError, errorInfo, _pinNumber);
7176
}
7277

7378
if (waitResult == WaitEventResult.EventOccured)
@@ -76,7 +81,7 @@ private Task InitializeEventDetectionTask(CancellationToken token, LineHandle pi
7681
int checkForEvent = LibgpiodV1.gpiod_line_event_read(pinHandle.Handle, ref eventResult);
7782
if (checkForEvent == -1)
7883
{
79-
throw ExceptionHelper.GetIOException(ExceptionResource.EventReadError, Marshal.GetLastWin32Error());
84+
throw ExceptionHelper.GetIOException(ExceptionResource.EventReadError, ExceptionHelper.GetLastErrorMessage());
8085
}
8186

8287
PinEventTypes eventType = eventResult.event_type == 1 ? PinEventTypes.Rising : PinEventTypes.Falling;

src/System.Device.Gpio/System/Device/Gpio/Drivers/RaspberryPi3LinuxDriver.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -616,21 +616,26 @@ private void Initialize()
616616
fileDescriptor = Interop.open(GpioMemoryFilePath, FileOpenFlags.O_RDWR | FileOpenFlags.O_SYNC);
617617
if (fileDescriptor == -1)
618618
{
619+
// Can't use ExceptionHelper.GetLastErrorMessage() here because we need the error code
620+
// for the ENOENT check. GetLastErrorMessage() would call GetLastWin32Error() internally,
621+
// and we can't call GetLastWin32Error() twice as subsequent calls might return different values.
619622
win32Error = Marshal.GetLastWin32Error();
623+
string errorMessage = Marshal.GetLastPInvokeErrorMessage();
620624

621625
// if the failure is NOT because /dev/gpiomem doesn't exist then throw an exception at this point.
622626
// if it were anything else then it is probably best not to try and use /dev/mem on the basis that
623627
// it would be better to solve the issue rather than use a method that requires root privileges
624628
if (win32Error != ENOENT)
625629
{
626-
throw new IOException($"Error {win32Error} initializing the Gpio driver.");
630+
string error = string.IsNullOrWhiteSpace(errorMessage) ? win32Error.ToString() : $"{win32Error} ({errorMessage})";
631+
throw new IOException($"Error {error} initializing the Gpio driver.");
627632
}
628633

629634
// if /dev/gpiomem doesn't seem to be available then let's try /dev/mem
630635
fileDescriptor = Interop.open(MemoryFilePath, FileOpenFlags.O_RDWR | FileOpenFlags.O_SYNC);
631636
if (fileDescriptor == -1)
632637
{
633-
throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver.");
638+
throw new IOException($"Error {ExceptionHelper.GetLastErrorMessage()} initializing the Gpio driver.");
634639
}
635640
else // success so set the offset into memory of the gpio registers
636641
{
@@ -668,7 +673,7 @@ private void Initialize()
668673
IntPtr mapPointer = Interop.mmap(IntPtr.Zero, Environment.SystemPageSize, (MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE), MemoryMappedFlags.MAP_SHARED, fileDescriptor, (int)gpioRegisterOffset);
669674
if (mapPointer.ToInt64() == -1)
670675
{
671-
throw new IOException($"Error {Marshal.GetLastWin32Error()} initializing the Gpio driver.");
676+
throw new IOException($"Error {ExceptionHelper.GetLastErrorMessage()} initializing the Gpio driver.");
672677
}
673678

674679
Interop.close(fileDescriptor);

src/System.Device.Gpio/System/Device/Gpio/Drivers/SysFsDriver.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,12 @@ private bool WasEventDetected(int pollFileDescriptor, int valueFileDescriptor, o
591591
continue;
592592
}
593593

594-
throw new IOException($"Error while waiting for pin interrupts. (ErrorCode={errorCode})");
594+
// Can't use ExceptionHelper.GetLastErrorMessage() here because we need the error code
595+
// for the EINTR check. GetLastErrorMessage() would call GetLastWin32Error() internally,
596+
// and we can't call GetLastWin32Error() twice as subsequent calls might return different values.
597+
string errorMessage = Marshal.GetLastPInvokeErrorMessage();
598+
string error = string.IsNullOrWhiteSpace(errorMessage) ? errorCode.ToString() : $"{errorCode} ({errorMessage})";
599+
throw new IOException($"Error while waiting for pin interrupts. (ErrorCode={error})");
595600
}
596601

597602
if (waitResult > 0)

src/System.Device.Gpio/System/Device/Gpio/ExceptionHelper.cs

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,98 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.IO;
5+
using System.Runtime.InteropServices;
56

67
namespace System.Device.Gpio;
78

89
internal static class ExceptionHelper
910
{
11+
/// <summary>
12+
/// Gets the last P/Invoke error code and message.
13+
/// This should be called immediately after a P/Invoke call that failed.
14+
/// </summary>
15+
/// <returns>A formatted string containing the error code and human-readable message</returns>
16+
internal static string GetLastErrorMessage()
17+
{
18+
int errorCode = Marshal.GetLastWin32Error();
19+
string errorMessage = Marshal.GetLastPInvokeErrorMessage();
20+
21+
if (string.IsNullOrWhiteSpace(errorMessage))
22+
{
23+
return errorCode.ToString();
24+
}
25+
26+
return $"{errorCode} ({errorMessage})";
27+
}
28+
29+
/// <summary>
30+
/// Formats an error code with optional error message into a display string.
31+
/// </summary>
32+
private static string FormatError(int errorCode, string? errorMessage = null)
33+
{
34+
if (errorCode == -1)
35+
{
36+
return string.Empty;
37+
}
38+
39+
if (string.IsNullOrWhiteSpace(errorMessage))
40+
{
41+
return errorCode.ToString();
42+
}
43+
44+
return $"{errorCode} ({errorMessage})";
45+
}
46+
1047
public static IOException GetIOException(ExceptionResource resource, int errorCode = -1, int pin = -1)
1148
{
12-
return new IOException(GetResourceString(resource, errorCode, pin));
49+
return new IOException(GetResourceString(resource, errorCode, pin, null));
50+
}
51+
52+
public static IOException GetIOException(ExceptionResource resource, string errorInfo, int pin = -1)
53+
{
54+
return new IOException(GetResourceString(resource, -1, pin, errorInfo));
1355
}
1456

1557
public static InvalidOperationException GetInvalidOperationException(ExceptionResource resource, int pin = -1)
1658
{
17-
return new InvalidOperationException(GetResourceString(resource, -1, pin));
59+
return new InvalidOperationException(GetResourceString(resource, -1, pin, null));
1860
}
1961

2062
public static PlatformNotSupportedException GetPlatformNotSupportedException(ExceptionResource resource)
2163
{
22-
return new PlatformNotSupportedException(GetResourceString(resource, -1, -1));
64+
return new PlatformNotSupportedException(GetResourceString(resource, -1, -1, null));
2365
}
2466

2567
public static ArgumentException GetArgumentException(ExceptionResource resource)
2668
{
27-
return new ArgumentException(GetResourceString(resource, -1, -1));
69+
return new ArgumentException(GetResourceString(resource, -1, -1, null));
2870
}
2971

30-
private static string GetResourceString(ExceptionResource resource, int errorCode, int pin) => resource switch
72+
private static string GetResourceString(ExceptionResource resource, int errorCode, int pin, string? errorInfo)
3173
{
32-
ExceptionResource.NoChipIteratorFound => $"Unable to find a chip iterator, error code: {errorCode}",
33-
ExceptionResource.NoChipFound => $"Unable to find a chip, error code: {errorCode}",
34-
ExceptionResource.PinModeReadError => $"Error while reading pin mode for pin: {pin}",
35-
ExceptionResource.OpenPinError => $"Pin {pin} not available, error: {errorCode}",
36-
ExceptionResource.ReadPinError => $"Error while reading value from pin: {pin}, error: {errorCode}",
37-
ExceptionResource.PinNotOpenedError => $"Can not access pin {pin} that is not open.",
38-
ExceptionResource.SetPinModeError => $"Error setting pin mode, for pin: {pin}, error: {errorCode}",
39-
ExceptionResource.ConvertPinNumberingSchemaError => $"This driver is generic so it cannot perform conversions between pin numbering schemes.",
40-
ExceptionResource.RequestEventError => $"Error while requesting event listener for pin {pin}, error code: {errorCode}",
41-
ExceptionResource.InvalidEventType => $"Invalid or not supported event type requested",
42-
ExceptionResource.EventWaitError => $"Error while waiting for event, error code {errorCode}, pin: {pin}",
43-
ExceptionResource.EventReadError => $"Error while reading pin event result, error code {errorCode}",
44-
ExceptionResource.NotListeningForEventError => $"Attempted to remove a callback for a pin that is not listening for events.",
45-
ExceptionResource.LibGpiodNotInstalled => $"Libgpiod driver not installed. More information on: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/",
46-
_ => throw new Exception($"The ExceptionResource enum value: {resource} is not part of the switch. Add the appropriate case and exception message."),
47-
};
74+
// Use errorInfo if provided, otherwise format errorCode
75+
string formattedError = errorInfo ?? FormatError(errorCode);
76+
string errorDisplay = string.IsNullOrEmpty(formattedError) ? string.Empty : $", error: {formattedError}";
77+
78+
return resource switch
79+
{
80+
ExceptionResource.NoChipIteratorFound => $"Unable to find a chip iterator{errorDisplay}",
81+
ExceptionResource.NoChipFound => $"Unable to find a chip{errorDisplay}",
82+
ExceptionResource.PinModeReadError => $"Error while reading pin mode for pin: {pin}",
83+
ExceptionResource.OpenPinError => $"Pin {pin} not available{errorDisplay}",
84+
ExceptionResource.ReadPinError => $"Error while reading value from pin: {pin}{errorDisplay}",
85+
ExceptionResource.PinNotOpenedError => $"Can not access pin {pin} that is not open.",
86+
ExceptionResource.SetPinModeError => $"Error setting pin mode, for pin: {pin}{errorDisplay}",
87+
ExceptionResource.ConvertPinNumberingSchemaError => $"This driver is generic so it cannot perform conversions between pin numbering schemes.",
88+
ExceptionResource.RequestEventError => $"Error while requesting event listener for pin {pin}{errorDisplay}",
89+
ExceptionResource.InvalidEventType => $"Invalid or not supported event type requested",
90+
ExceptionResource.EventWaitError => $"Error while waiting for event{errorDisplay}, pin: {pin}",
91+
ExceptionResource.EventReadError => $"Error while reading pin event result{errorDisplay}",
92+
ExceptionResource.NotListeningForEventError => $"Attempted to remove a callback for a pin that is not listening for events.",
93+
ExceptionResource.LibGpiodNotInstalled => $"Libgpiod driver not installed. More information on: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/",
94+
_ => throw new Exception($"The ExceptionResource enum value: {resource} is not part of the switch. Add the appropriate case and exception message."),
95+
};
96+
}
4897
}
4998

5099
internal enum ExceptionResource

src/System.Device.Gpio/System/Device/I2c/UnixI2cBus.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Concurrent;
55
using System.Collections.Generic;
6+
using System.Device.Gpio;
67
using System.Globalization;
78
using System.IO;
89
using System.Runtime.InteropServices;
@@ -24,7 +25,7 @@ internal class UnixI2cBus : I2cBus
2425

2526
if (busFileDescriptor < 0)
2627
{
27-
throw new IOException($"Error {Marshal.GetLastWin32Error()}. Can not open I2C device file '{deviceFileName}'.");
28+
throw new IOException($"Error {ExceptionHelper.GetLastErrorMessage()}. Can not open I2C device file '{deviceFileName}'.");
2829
}
2930

3031
I2cFunctionalityFlags functionalityFlags;
@@ -194,7 +195,7 @@ protected virtual unsafe void WriteReadCore(ushort deviceAddress, byte* writeBuf
194195
int result = Interop.ioctl(BusFileDescriptor, (uint)I2cSettings.I2C_RDWR, new IntPtr(&msgset));
195196
if (result < 0)
196197
{
197-
throw new IOException($"Error {Marshal.GetLastWin32Error()} performing I2C data transfer.");
198+
throw new IOException($"Error {ExceptionHelper.GetLastErrorMessage()} performing I2C data transfer.");
198199
}
199200
}
200201

src/System.Device.Gpio/System/Device/I2c/UnixI2cFileTransferBus.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Device.Gpio;
45
using System.IO;
56
using System.Runtime.InteropServices;
67

@@ -18,15 +19,15 @@ protected override unsafe void WriteReadCore(ushort deviceAddress, byte* writeBu
1819
int result = Interop.ioctl(BusFileDescriptor, (uint)I2cSettings.I2C_SLAVE_FORCE, deviceAddress);
1920
if (result < 0)
2021
{
21-
throw new IOException($"Error {Marshal.GetLastWin32Error()} performing I2C data transfer.");
22+
throw new IOException($"Error {ExceptionHelper.GetLastErrorMessage()} performing I2C data transfer.");
2223
}
2324

2425
if (writeBuffer != null)
2526
{
2627
result = Interop.write(BusFileDescriptor, new IntPtr(writeBuffer), writeBufferLength);
2728
if (result < 0)
2829
{
29-
throw new IOException($"Error {Marshal.GetLastWin32Error()} performing I2C data transfer.");
30+
throw new IOException($"Error {ExceptionHelper.GetLastErrorMessage()} performing I2C data transfer.");
3031
}
3132
}
3233

@@ -35,7 +36,7 @@ protected override unsafe void WriteReadCore(ushort deviceAddress, byte* writeBu
3536
result = Interop.read(BusFileDescriptor, new IntPtr(readBuffer), readBufferLength);
3637
if (result < 0)
3738
{
38-
throw new IOException($"Error {Marshal.GetLastWin32Error()} performing I2C data transfer.");
39+
throw new IOException($"Error {ExceptionHelper.GetLastErrorMessage()} performing I2C data transfer.");
3940
}
4041
}
4142
}

0 commit comments

Comments
 (0)