-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gracefully handle FT_OTHER_ERROR for FT_GetDeviceInfo #37
Conversation
For devices that do not have a configured EEPROM attached, there are no USB string descriptors present. Under this condition, FT_GetDeviceInfo will return FT_OTHER_ERROR. Static analysis of this function indicates that all fields except serial_number and description are valid for FT_OTHER_ERROR. These string parameters are unmodified in this case.
I had the same thing happen with my second FT232H from Adafruit. I assumed it was a one-off manufacturing error because the first one I have (purchased years earlier) worked fine. I guess they made a few with the same problem.
There may be some There may be multiple conditions in this function that return Alternatively, maybe we could sidestep the problem entirely and return the Something along the lines of: struct DeviceInfoError {
device_info: DeviceInfo,
status: FtStatus,
}
// add comment about known FT_OTHER_ERROR conditions here
fn device_info(&mut self) -> Result<DeviceInfo, DeviceInfoError> {
let mut device_type: u32 = 0;
let mut device_id: u32 = 0;
let mut serial_number: [i8; STRING_LEN] = [0; STRING_LEN];
let mut description: [i8; STRING_LEN] = [0; STRING_LEN];
trace!("FT_GetDeviceInfo({:?}, _, _, _, _, NULL)", self.handle());
let status: FT_STATUS = unsafe {
FT_GetDeviceInfo(
self.handle(),
&mut device_type,
&mut device_id,
serial_number.as_mut_ptr() as *mut c_char,
description.as_mut_ptr() as *mut c_char,
std::ptr::null_mut(),
)
};
let (vid, pid) = vid_pid_from_id(device_id);
let device_info = DeviceInfo {
port_open: true,
speed: None,
device_type: device_type.into(),
product_id: pid,
vendor_id: vid,
serial_number: slice_into_string(serial_number.as_ref()),
description: slice_into_string(description.as_ref()),
};
if status != 0 {
Err(DeviceInfoError { device_info, status: status.into() })
} else {
Ok(device_info)
}
} What are your thoughts here?
Yeah... The vendor library is pretty bad on linux... I had to add |
src/lib.rs
Outdated
serial_number: match status { | ||
FT_OK => slice_into_string(serial_number.as_ref()), | ||
_ => String::default(), | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slice_to_string
should handle empty strings.
serial_number: match status { | |
FT_OK => slice_into_string(serial_number.as_ref()), | |
_ => String::default(), | |
}, | |
serial_number: slice_into_string(serial_number.as_ref()), |
The main issue I'm facing isn't calls to I'm not really equipped for Windows development at the moment, but it's a good point that there could be a completely separate set of |
Are the ffi types for the |
Ah, good point.
They are an enum in the original |
Ahh, good to know about the types. Didn't realize the bindings are generated. I tried making sense of the Windows implementation of GetDeviceInfo, but the bulk of it is done in kernel mode and I don't really have to willpower to untangle that mess. 😅 |
No worries, my willpower to develop on Windows is low too 👍 In the meantime, if you want to put valid settings on your EEPROM I used this python CLI tool to put valid settings onto mine: https://eblot.github.io/pyftdi/eeprom.html |
Cool! Thanks for that. Though I'm still hoping users of my application would be able to use their unprogammed devices if at all possible. I reached out to FTDI to set the record straight on what FT_OTHER_ERROR actually means. I hope that this code can be made into a stable, well documented API semantic. |
Got a response back from FTDI. Their current position is WONTFIX due to their inability to reproduce the issue and lack of reports from other customers. This is in spite of me providing detailed information about what causes the return code, concerns about the ambiguity of such an error code in the first place, and a parsed dump of my USB descriptors. Though I've never had much luck dealing with "Technical Support Engineers" in the past, so I can't say I'm surprised. |
That's unfortunate, but I am not surprised. I submitted some documentation bugs to them a while ago (practically free to fix) and the same issues are still outstanding a year later. Ideas:
|
What's the behavior on Windows out of curiosity? Do none of the fields get returned for an unprogrammed EEPROM? What's the return code in that case? |
No idea unfortunately, I loaded mine with a valid EEPROM without ever using it on Windows. |
I just tried out my board on Windows. Serial: FTXZ7WQM I tried it again on Linux just in case it sneakily programmed my device for me. Nope, still no descriptors, still |
Also had a look at the macOS version. It's essentially the same as the Linux version, returning |
Can you hexdump your eeprom with something like pyftdi? If there are valid values in there we could just dump the EEPROM to check the device type. This is definitely a bug in libftd2xx now though... Platform specific behavior for the same function is never desirable. |
Oh! Looks like my EEPROM is programmed after all. Is it malformed in some way?
EDIT: Ah-ha! The string indices are all 128 bytes ahead of the actual data. As if it's expecting to be put on a 128-byte EEPROM and not a 256-byte one. So yeah, this would appear to be an Adafruit issue. I can only assume the Windows driver simply does its own EEPROM read and assumes it's 128 bytes and masks the address accordingly. |
How would you feel about adding this to Type constants are from pyftdi. It doesn't have exhaustive coverage of /// Identify device type with a single EEPROM read.
///
/// # Example
///
/// ```no_run
/// use libftd2xx::{Ftdi, FtdiCommon};
///
/// let mut ft = Ftdi::new()?;
/// let dev_type = ft.device_type()?;
/// println!("Device type: {:?}", dev_type);
/// # Ok::<(), libftd2xx::FtStatus>(())
/// ```
fn device_type(&mut self) -> Result<DeviceType, FtStatus> {
match self.eeprom_word_read(0x3)? {
0x0200 => Ok(DeviceType::FTAM),
0x0400 => Ok(DeviceType::FTBM),
0x0500 => Ok(DeviceType::FT2232C),
0x0600 => Ok(DeviceType::FT232R),
0x0700 => Ok(DeviceType::FT2232H),
0x0800 => Ok(DeviceType::FT4232H),
0x0900 => Ok(DeviceType::FT232H),
0x1000 => Ok(DeviceType::FT_X_SERIES),
_ => Err(FtStatus::NOT_SUPPORTED)
}
} |
Managed to fill in the last four from an obvious switch statement in I put the panic in there to match the one in fn device_type(&mut self) -> Result<DeviceType, FtStatus> {
Ok(match self.eeprom_word_read(0x3)? {
0x0200 => DeviceType::FTAM,
0x0400 => DeviceType::FTBM,
// ??? => DeviceType::FT100AX,
0x0500 => DeviceType::FT2232C,
0x0600 => DeviceType::FT232R,
0x0700 => DeviceType::FT2232H,
0x0800 => DeviceType::FT4232H,
0x0900 => DeviceType::FT232H,
0x1000 => DeviceType::FT_X_SERIES,
0x1700 => DeviceType::FT4222H_3,
0x1800 => DeviceType::FT4222H_0,
0x1900 => DeviceType::FT4222H_1_2,
0x2100 => DeviceType::FT4222_PROG,
value => panic!("unknown device type in EEPROM: {}", value),
})
} |
I think the vendor function may actually be keying off of the VID/PID and not the EEPROM (maybe?)
I wrote this back when I was still learning rust, I should probably fix that at some point 😨, ideally we can return an error instead. Something along the lines of: /// Identify device type.
///
/// This will attempt to identify the device using the the [`device_info`]
/// method, if that method fails it will then try to determine the device type
/// from the value stored in the EEPROM.
/// If the EEPROM value does not match a known device this function returns
/// [`FtStatus::OTHER_ERROR`], though there may be other conditions in the
/// vendor driver that also return this code.
///
/// This is not a native function in `libftd2xx`, this works around a bug in
/// `libftd2xx`, see https://github.com/newAM/libftd2xx-rs/pull/37 for more
/// information.
///
/// # Example
///
/// ```no_run
/// use libftd2xx::{Ftdi, FtdiCommon};
///
/// let mut ft = Ftdi::new()?;
/// let dev_type = ft.device_type()?;
/// println!("Device type: {:?}", dev_type);
/// # Ok::<(), libftd2xx::FtStatus>(())
/// ```
fn device_type(&mut self) -> Result<DeviceType, FtStatus> {
if let Ok(info) = self.device_info() {
Ok(info.device_type)
} else {
match self.eeprom_word_read(0x3)? {
0x0200 => DeviceType::FTAM,
0x0400 => DeviceType::FTBM,
// ??? => DeviceType::FT100AX,
0x0500 => DeviceType::FT2232C,
0x0600 => DeviceType::FT232R,
0x0700 => DeviceType::FT2232H,
0x0800 => DeviceType::FT4232H,
0x0900 => DeviceType::FT232H,
0x1000 => DeviceType::FT_X_SERIES,
0x1700 => DeviceType::FT4222H_3,
0x1800 => DeviceType::FT4222H_0,
0x1900 => DeviceType::FT4222H_1_2,
0x2100 => DeviceType::FT4222_PROG,
_ => Err(FtStatus::OTHER_ERROR)
}
}
} Then for each specific device that implements impl FtdiCommon for Ft232h {
fn handle(&mut self) -> FT_HANDLE {
self.handle
}
fn device_type(&mut self) -> Result<Ftdi, FtStatus> {
Ok(DeviceType::FT232H)
}
} What do you think? |
I like this idea. The EEPROM read is essentially how pyftdi does device selection, seems like a good fallback. I'll update the PR in a bit. |
For devices that do not have a configured EEPROM attached, there are no USB string descriptors present. Under this condition,
FT_GetDeviceInfo
will returnFT_OTHER_ERROR
. This is the case for an Adafruit FT232H module with factory configuration and libftd2xx 1.4.24 (current x86_64 Linux release at time of writing).Static analysis of this function indicates that all fields except
serial_number
anddescription
are valid forFT_OTHER_ERROR
. These string parameters are unmodified in this case.This really seems to be a bug in FTDI's implementation. It also causes internal misbehaviors that affect the output of
FT_GetDeviceInfoList
(all zero fields).