drivers: w1 introduce 1-wire net layer api

This commit introduces a new api for the net layer of Dallas
1-wire protocol.
For single drop configurations w1_read_rom, and w1_skip_rom commands are
provided.
For multidrop configurations w1_match_rom, w1_resume_command,
w1_search_rom, and w1_search_alarm routines are provided as well.
Additionally, the reset_select routine, conditionally depending on the
bus configuration, either executes a match_rom, or a skip rom command.
A w1_write_read command simplifies the typical scenario of addressing
a device, writing a few bytes to the device and reading back the answer.

Additionally w1_crc8, and w1_crc16 are added as wrappers around the
zephyr in-tree crc8 and crc16_reflect implementations.
The former may be used to verify the validity of the rom id, while the
latter is used for integrity checking of many eeprom, and authenticator
commands.

The general search command does not support filtering on
the family code.

Signed-off-by: Thomas Stranger <thomas.stranger@outlook.com>
This commit is contained in:
Thomas Stranger 2022-07-03 20:00:23 +02:00 committed by Carles Cufí
commit bb4f7b5a31
5 changed files with 764 additions and 0 deletions

View file

@ -7,3 +7,10 @@ zephyr_library_sources(w1_common.c)
# drivers implementing link functions (read, write, reset)
# none implemented yet
# network functions:
if(CONFIG_W1_NET)
# network functions should be callable from user as well as supervisor mode:
remove_definitions(-D__ZEPHYR_SUPERVISOR__)
zephyr_library_sources(w1_net.c)
endif() #CONFIG_W1_NET

View file

@ -22,4 +22,10 @@ config W1_INIT_PRIORITY
help
1-Wire device driver initialization priority.
config W1_NET
bool "1-Wire network layer"
default y
help
Enable 1-wire network layer
endif # W1

View file

@ -93,3 +93,24 @@ static inline size_t z_vrfy_w1_get_slave_count(const struct device *dev)
return z_impl_w1_get_slave_count((const struct device *)dev);
}
#include <syscalls/w1_get_slave_count_mrsh.c>
#if CONFIG_W1_NET
static inline int z_vrfy_w1_search_bus(const struct device *dev,
uint8_t command, uint8_t family,
w1_search_callback_t callback,
void *user_data)
{
Z_OOPS(Z_SYSCALL_OBJ(dev, K_OBJ_DRIVER_W1));
Z_OOPS(Z_SYSCALL_VERIFY_MSG(callback == 0,
"callbacks may not be set from user mode"));
/* user_data is not dereferenced, no need to check parameter */
return z_impl_w1_search_bus((const struct device *)dev,
(uint8_t)command, (uint8_t)family,
(w1_search_callback_t)callback,
(void *)user_data);
}
#include <syscalls/w1_search_bus_mrsh.c>
#endif /* CONFIG_W1_NET */

398
drivers/w1/w1_net.c Normal file
View file

@ -0,0 +1,398 @@
/*
* Copyright (c) 2022 Thomas Stranger
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief 1-Wire network related functions.
*
* The following procedures wrap basic w1 syscalls, they should be callable
* from user mode as well as supervisor mode, therefore _ZEPHYR_SUPERVISOR__
* is not defined for this file such that inline macros do not skip
* the arch_is_user_context() check.
*/
#include <logging/log.h>
#include <drivers/w1.h>
LOG_MODULE_REGISTER(w1, CONFIG_W1_LOG_LEVEL);
#define W1_SEARCH_DISCREPANCY_INIT 0
#define W1_SEARCH_LAST_SLAVE 65
#define W1_SEARCH_NO_SLAVE 66
/* @brief Search bus for next slave.
*
* This function searches the next 1-Wire slave on the bus.
* It sets the found ROM and the last discrepancy in case more than one
* slave took part in the search.
* In case only one slave took part in the search, the discrepancy is set to
* W1_SEARCH_LAST_SLAVE, and in case no slave participated in the search,
* the discrepancy is set to W1_SEARCH_NO_SLAVE.
*
* The implementation is similar to suggested in the maxim application note 187.
* The master reads the first ROM bit and its complementary value of all slaves.
* Due to physical characteristics, the value received is a
* logical AND of all slaves' 1st bit. Slaves only continue to
* participate in the search procedure if the next bit the master sends matches
* their own addresses' bit. This allows the master to branch through 64-bit
* addresses in order to detect all slaves.
* The 1st bit received is stored in bit 1 of rom_inv_64, the 2nd in bit 2 and so
* on, until bit 64.
* As a result, each byte of the ROM has the correct bit order, but the received
* bytes (big-endian) stored in rom_inv_64 are in inverse byte order.
*
* Note: Filtering by families is currently not supported.
*
* @param dev Pointer to the device structure for the w1 instance.
* @param command Command to chose between normal and alarm search.
* @param family This parameter is currently not supported.
* @param last_discrepancy This must be set to W1_SEARCH_DISCREPANCY_INIT before
* the first call, it carries the search progress for
* further calls.
* @param rom_inv_64 The found ROM: It must be set to zero before first
* call and carries the last found ROM for furter calls.
* The ROM is stored in inverse byte order.
*
* @retval 0 If successful.
* @retval -errno Negative error code in case of 1-wire read/write error.
*/
static int search_slave(const struct device *dev, uint8_t command,
uint8_t family, size_t *last_discrepancy,
uint64_t *rom_inv_64)
{
int ret;
size_t next_discrepancy;
bool last_id_bit;
bool last_complement_id_bit;
ARG_UNUSED(family);
__ASSERT_NO_MSG(command == W1_CMD_SEARCH_ROM ||
command == W1_CMD_SEARCH_ALARM);
ret = w1_reset_bus(dev);
if (ret < 0) {
return ret;
}
if (ret == 0) {
*last_discrepancy = W1_SEARCH_NO_SLAVE;
return 0;
}
ret = w1_write_byte(dev, command);
if (ret < 0) {
return ret;
}
next_discrepancy = W1_SEARCH_LAST_SLAVE;
for (size_t id_bit_nr = 1; id_bit_nr < W1_SEARCH_LAST_SLAVE; id_bit_nr++) {
ret = w1_read_bit(dev);
if (ret < 0) {
return ret;
}
last_id_bit = (bool)ret;
ret = w1_read_bit(dev);
if (ret < 0) {
return ret;
}
last_complement_id_bit = (bool)ret;
if (last_id_bit && last_complement_id_bit) {
/*
* No slave participating:
* We can stop following the branch.
*/
LOG_DBG("No slave paricipating");
*last_discrepancy = W1_SEARCH_NO_SLAVE;
return 0;
} else if (last_id_bit != last_complement_id_bit) {
/*
* All slaves connected have same ROM bit value:
* We can directly follow last_id_bit branch.
*/
} else {
/*
* Discrepancy detected: bit value at id_bit_nr does
* not match for all slaves on the bus.
*/
if ((id_bit_nr > *last_discrepancy) ||
((id_bit_nr < *last_discrepancy) &&
(*rom_inv_64 & BIT64(id_bit_nr - 1)))) {
/*
* - id_bit_nr > last_discrepancy:
* Start always w/ branch of 1s
* - id_bit_nr < last_discrepancy:
* Follow same branch as before
*/
last_id_bit = true;
next_discrepancy = id_bit_nr;
} else {
/*
* - id_bit_nr == last_discrepancy:
* 1-path already done, therefore go 0 path
* - id_bit_nr < last_discrepancy:
* Follow same branch as before
*/
}
}
/*
* Send and store the chosen bit: all not matching slaves will
* no longer participate in this search until they are reset.
*/
ret = w1_write_bit(dev, last_id_bit);
if (ret < 0) {
return ret;
}
*rom_inv_64 &= ~BIT64(id_bit_nr - 1);
*rom_inv_64 |= last_id_bit ? BIT64(id_bit_nr - 1) : 0;
}
*last_discrepancy = next_discrepancy;
return 0;
}
int z_impl_w1_search_bus(const struct device *dev, uint8_t command,
uint8_t family, w1_search_callback_t callback,
void *user_data)
{
size_t last_discrepancy = W1_SEARCH_DISCREPANCY_INIT;
uint64_t found_rom_inv_64 = 0;
struct w1_rom found_rom = { 0 };
int found_cnt = 0;
int ret;
(void)w1_lock_bus(dev);
do {
ret = search_slave(dev, command, family, &last_discrepancy,
&found_rom_inv_64);
if (ret < 0) {
found_cnt = ret;
break;
}
if (last_discrepancy == W1_SEARCH_NO_SLAVE) {
break;
}
found_cnt++;
/*
* ROM is stored in found_rom_inv_64 in "inverse byte order" =>
* Only big-endian targets need to swap, such that struct's
* bytes are stored in big-endian byte order.
*/
if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) {
sys_memcpy_swap(&found_rom, &found_rom_inv_64, 8);
} else {
*(uint64_t *)&found_rom = found_rom_inv_64;
}
LOG_DBG("ROM found: nr %u, %016llx", found_cnt,
w1_rom_to_uint64(&found_rom));
if (callback != NULL) {
callback(found_rom, user_data);
}
} while (last_discrepancy != W1_SEARCH_LAST_SLAVE);
(void)w1_unlock_bus(dev);
return found_cnt;
}
int w1_read_rom(const struct device *dev, struct w1_rom *rom)
{
int ret;
(void)w1_lock_bus(dev);
ret = w1_reset_bus(dev);
if (ret == 0) {
ret = -ENODEV;
goto out;
}
if (ret < 0) {
goto out;
}
ret = w1_write_byte(dev, W1_CMD_READ_ROM);
if (ret < 0) {
goto out;
}
ret = w1_read_block(dev, (uint8_t *)rom, sizeof(struct w1_rom));
if (ret < 0) {
goto out;
}
if (w1_crc8((uint8_t *)rom, sizeof(struct w1_rom)) != 0) {
ret = -EIO;
}
out:
(void)w1_unlock_bus(dev);
return ret;
};
static int match_rom(const struct device *dev, const struct w1_slave_config *config)
{
int ret;
uint8_t cmd;
if (!config->overdrive) {
if (w1_configure(dev, W1_SETTING_SPEED, 0) < 0) {
return -EIO;
}
}
ret = w1_reset_bus(dev);
if (ret == 0) {
return -ENODEV;
}
if (ret < 0) {
return ret;
}
cmd = config->overdrive ? W1_CMD_OVERDRIVE_MATCH_ROM : W1_CMD_MATCH_ROM;
ret = w1_write_byte(dev, cmd);
if (ret < 0) {
return ret;
}
ret = w1_write_block(dev, (uint8_t *)&config->rom, 8);
if (ret < 0) {
return ret;
}
if (config->overdrive) {
if (w1_configure(dev, W1_SETTING_SPEED, 1) < 0) {
return -EIO;
}
}
return 0;
};
int w1_match_rom(const struct device *dev, const struct w1_slave_config *config)
{
int ret;
(void)w1_lock_bus(dev);
ret = match_rom(dev, config);
(void)w1_unlock_bus(dev);
return ret;
}
int w1_resume_command(const struct device *dev)
{
int ret;
(void)w1_lock_bus(dev);
ret = w1_reset_bus(dev);
if (ret == 0) {
ret = -ENODEV;
goto out;
}
if (ret < 0) {
goto out;
}
ret = w1_write_byte(dev, W1_CMD_RESUME);
out:
(void)w1_unlock_bus(dev);
return ret;
}
static int skip_rom(const struct device *dev, const struct w1_slave_config *config)
{
int ret;
uint8_t cmd;
if (!config->overdrive) {
if (w1_configure(dev, W1_SETTING_SPEED, 0) < 0) {
return -EIO;
}
}
ret = w1_reset_bus(dev);
if (ret == 0) {
return -ENODEV;
}
if (ret < 0) {
return ret;
}
cmd = config->overdrive ? W1_CMD_OVERDRIVE_SKIP_ROM : W1_CMD_SKIP_ROM;
ret = w1_write_byte(dev, cmd);
if (ret < 0) {
return ret;
}
if (config->overdrive) {
if (w1_configure(dev, W1_SETTING_SPEED, 1) < 0) {
return -EIO;
}
}
return 0;
}
int w1_skip_rom(const struct device *dev, const struct w1_slave_config *config)
{
int ret;
(void)w1_lock_bus(dev);
ret = skip_rom(dev, config);
(void)w1_unlock_bus(dev);
return ret;
}
static int reset_select(const struct device *dev, const struct w1_slave_config *config)
{
if (w1_get_slave_count(dev) > 1) {
return match_rom(dev, config);
}
return skip_rom(dev, config);
}
int w1_reset_select(const struct device *dev, const struct w1_slave_config *config)
{
int ret;
(void)w1_lock_bus(dev);
ret = reset_select(dev, config);
(void)w1_unlock_bus(dev);
return ret;
}
static int write_read(const struct device *dev, const struct w1_slave_config *config,
const uint8_t *write_buf, size_t write_len,
uint8_t *read_buf, size_t read_len)
{
int ret;
ret = reset_select(dev, config);
if (ret != 0) {
return ret;
}
ret = w1_write_block(dev, write_buf, write_len);
if (ret < 0) {
return ret;
}
if (read_buf == NULL && read_len > 0) {
return -EIO;
}
return w1_read_block(dev, read_buf, read_len);
};
int w1_write_read(const struct device *dev, const struct w1_slave_config *config,
const uint8_t *write_buf, size_t write_len,
uint8_t *read_buf, size_t read_len)
{
int ret;
(void)w1_lock_bus(dev);
ret = write_read(dev, config, write_buf, write_len, read_buf, read_len);
(void)w1_unlock_bus(dev);
return ret;
};

View file

@ -325,6 +325,338 @@ static inline int z_impl_w1_configure(const struct device *dev,
return api->configure(dev, type, value);
}
/**
* @}
*/
/**
* @brief 1-Wire network layer
* @defgroup w1_network 1-Wire network layer
* @ingroup w1_interface
* @{
*/
/**
* @name 1-Wire ROM Commands
* @{
*/
#define W1_CMD_SKIP_ROM 0xCC
#define W1_CMD_MATCH_ROM 0x55
#define W1_CMD_RESUME 0xA5
#define W1_CMD_READ_ROM 0x33
#define W1_CMD_SEARCH_ROM 0xF0
#define W1_CMD_SEARCH_ALARM 0xEC
#define W1_CMD_OVERDRIVE_SKIP_ROM 0x3C
#define W1_CMD_OVERDRIVE_MATCH_ROM 0x69
/** @} */
/**
* @name CRC Defines
* @{
*/
/** Seed value used to calculate the 1-Wire 8-bit crc. */
#define W1_CRC8_SEED 0x00
/** Polynomial used to calculate the 1-Wire 8-bit crc. */
#define W1_CRC8_POLYNOMIAL 0x8C
/** Seed value used to calculate the 1-Wire 16-bit crc. */
#define W1_CRC16_SEED 0x0000
/** Polynomial used to calculate the 1-Wire 16-bit crc. */
#define W1_CRC16_POLYNOMIAL 0xa001
/** @} */
/** This flag can be passed to searches in order to not filter on family ID. */
#define W1_SEARCH_ALL_FAMILIES 0x00
/** Intitialize all w1_rom struct members to zero. */
#define W1_ROM_INIT_ZERO \
{ \
.family = 0, .serial = { 0 }, .crc = 0, \
}
/**
* @brief w1_rom struct.
*/
struct w1_rom {
/** @brief The 1-Wire family code identifying the slave device type.
*
* An incomplete list of family codes is available at:
* https://www.maximintegrated.com/en/app-notes/index.mvp/id/155
* others are documented in the respective device data sheet.
*/
uint8_t family;
/** The serial together with the family code composes the unique 56-bit id */
uint8_t serial[6];
/** 8-bit checksum of the 56-bit unique id. */
uint8_t crc;
};
/**
* @brief Node specific 1-wire configuration struct.
*
* This struct is passed to network functions, such that they can configure
* the bus to address the specific slave using the selected speed.
*/
struct w1_slave_config {
/** Unique 1-Wire ROM. */
struct w1_rom rom;
/** overdrive speed is used if set to 1. */
uint32_t overdrive : 1;
/** @cond INTERNAL_HIDDEN */
uint32_t res : 31;
/** @endcond */
};
/**
* @brief Define the application callback handler function signature
* for searches.
*
* @param rom found The ROM of the found slave.
* @param user_data User data provided to the w1_search_bus() call.
*/
typedef void (*w1_search_callback_t)(struct w1_rom rom, void *user_data);
/**
* @brief Read Peripheral 64-bit ROM.
*
* This procedure allows the 1-Wire bus master to read the peripherals
* 64-bit ROM without using the Search ROM procedure.
* This command can be used as long as not more than a sigle peripheral is
* connected to the bus.
* Otherwise data collisons occur and a faulty ROM is read.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param[out] rom Pointer to the ROM structure.
*
* @retval 0 If successful.
* @retval -ENODEV In case no slave responds to reset.
* @retval -errno Other negative error code in case of invalid crc and
* communication errors.
*/
int w1_read_rom(const struct device *dev, struct w1_rom *rom);
/**
* @brief Select a specific slave by broadcasting a selected ROM.
*
* This routine allows the 1-Wire bus master to select a slave
* identified by its unique ROM, such that the next command will target only
* this single selected slave.
*
* This command is only necessary in multidrop environments, otherwise the
* Skip ROM command can be issued.
* Once a slave has been selected, to reduce the communication overhead, the
* resume command can be used instead of this command to communicate with the
* selected slave.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param[in] config Pointer to the slave specific 1-Wire config.
*
* @retval 0 If successful.
* @retval -ENODEV In case no slave responds to reset.
* @retval -errno Other negative error code on error.
*/
int w1_match_rom(const struct device *dev, const struct w1_slave_config *config);
/**
* @brief Select the slave last addressed with a Match ROM or Search ROM commnad.
*
* This routine allows the 1-Wire bus master to re-select a slave
* device that was already addressed using a Match ROM or Search ROM command.
*
* @param dev Pointer to the device structure for the driver instance.
*
* @retval 0 If successful.
* @retval -ENODEV In case no slave responds to reset.
* @retval -errno Other negative error code on error.
*/
int w1_resume_command(const struct device *dev);
/**
* @brief Select all slaves regardless of ROM.
*
* This routine sets up the bus slaves to receive a command.
* It is usually used when there is only one peripheral on the bus
* to avoid the overhead of the Match ROM command.
* But it can also be used to concurrently write to all slave devices.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param[in] config Pointer to the slave specific 1-Wire config.
*
* @retval 0 If successful.
* @retval -ENODEV In case no slave responds to reset.
* @retval -errno Other negative error code on error.
*/
int w1_skip_rom(const struct device *dev, const struct w1_slave_config *config);
/**
* @brief In single drop configurations use Skip Select command, otherweise use
* Match ROM command.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param[in] config Pointer to the slave specific 1-Wire config.
*
* @retval 0 If successful.
* @retval -ENODEV In case no slave responds to reset.
* @retval -errno Other negative error code on error.
*/
int w1_reset_select(const struct device *dev, const struct w1_slave_config *config);
/**
* @brief Write then read data from the 1-Wire slave with matching ROM.
*
* This routine uses w1_reset_select to select the given ROM.
* Then writes given data and reads the response back from the slave.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param[in] config Pointer to the slave specific 1-Wire config.
* @param[in] write_buf Pointer to the data to be written.
* @param write_len Number of bytes to write.
* @param[out] read_buf Pointer to storage for read data.
* @param read_len Number of bytes to read.
*
* @retval 0 If successful.
* @retval -ENODEV In case no slave responds to reset.
* @retval -errno Other negative error code on error.
*/
int w1_write_read(const struct device *dev, const struct w1_slave_config *config,
const uint8_t *write_buf, size_t write_len,
uint8_t *read_buf, size_t read_len);
/**
* @brief Search 1-wire slaves on the bus.
*
* This function searches slaves on the 1-wire bus, with the possibility
* to search either all slaves or only slaves that have an active alarm state.
* If a callback is passed, the callback is called for each found slave.
*
* The algorithm mostly follows the suggestions of
* https://pdfserv.maximintegrated.com/en/an/AN187.pdf
*
* Note: Filtering on families is not supported.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param command Can either be W1_SEARCH_ALARM or W1_SEARCH_ROM.
* @param family W1_SEARCH_ALL_FAMILIES searcheas all families,
* filtering on a specific family is not yet supported.
* @param callback Application callback handler function to be called
* for each found slave.
* @param[in] user_data User data to pass to the application callback handler
* function.
*
* @retval slave_count Number of slaves found.
* @retval -errno Negative error code on error.
*/
__syscall int w1_search_bus(const struct device *dev, uint8_t command,
uint8_t family, w1_search_callback_t callback,
void *user_data);
/**
* @brief Search for 1-Wire slave on bus.
*
* This routine can discover unknown slaves on the bus by scanning for the
* unique 64-bit registration number.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param callback Application callback handler function to be called
* for each found slave.
* @param[in] user_data User data to pass to the application callback handler
* function.
*
* @retval slave_count Number of slaves found.
* @retval -errno Negative error code on error.
*/
static inline int w1_search_rom(const struct device *dev,
w1_search_callback_t callback, void *user_data)
{
return w1_search_bus(dev, W1_CMD_SEARCH_ROM, W1_SEARCH_ALL_FAMILIES,
callback, user_data);
}
/**
* @brief Search for 1-Wire slaves with an active alarm.
*
* This routine searches 1-Wire slaves on the bus, which currently have
* an active alarm.
*
* @param[in] dev Pointer to the device structure for the driver instance.
* @param callback Application callback handler function to be called
* for each found slave.
* @param[in] user_data User data to pass to the application callback handler
* function.
*
* @retval slave_count Number of slaves found.
* @retval -errno Negative error code on error.
*/
static inline int w1_search_alarm(const struct device *dev,
w1_search_callback_t callback, void *user_data)
{
return w1_search_bus(dev, W1_CMD_SEARCH_ALARM, W1_SEARCH_ALL_FAMILIES,
callback, user_data);
}
/**
* @brief Function to convert a w1_rom struct to an uint64_t.
*
* @param[in] rom Pointer to the ROM struct.
*
* @retval rom64 The ROM converted to an unsigned integer in endianness.
*/
static inline uint64_t w1_rom_to_uint64(const struct w1_rom *rom)
{
return sys_get_be64((uint8_t *)rom);
}
/**
* @brief Function to write an uint64_t to struct w1_rom pointer.
*
* @param rom64 Unsigned 64 bit integer representing the ROM in host endianness.
* @param[out] rom The ROM struct pointer.
*/
static inline void w1_uint64_to_rom(const uint64_t rom64, struct w1_rom *rom)
{
sys_put_be64(rom64, (uint8_t *)rom);
}
/**
* @brief Compute CRC-8 chacksum as defined in the 1-Wire specification.
*
* The 1-Wire of CRC 8 variant is using 0x31 as its polynomial with the initial
* value set to 0x00.
* This CRC is used to check the correctness of the unique 56-bit ROM.
*
* @param[in] src Input bytes for the computation.
* @param len Length of the input in bytes.
*
* @retval crc The computed CRC8 value.
*/
static inline uint8_t w1_crc8(const uint8_t *src, size_t len)
{
return crc8(src, len, W1_CRC8_POLYNOMIAL, W1_CRC8_SEED, true);
}
/**
* @brief Compute 1-Wire variant of CRC 16
*
* The 16-bit 1-Wire crc variant is using the reflected polynomial function
* X^16 + X^15 * + X^2 + 1 with the initial value set to 0x0000.
* See also APPLICATION NOTE 27:
* "UNDERSTANDING AND USING CYCLIC REDUNDANCY CHECKS WITH MAXIM 1-WIRE AND IBUTTON PRODUCTS"
* https://www.maximintegrated.com/en/design/technical-documents/app-notes/2/27.html
*
* @param seed Init value for the CRC, it is usually set to 0x0000.
* @param[in] src Input bytes for the computation.
* @param len Length of the input in bytes.
*
* @retval crc The computed CRC16 value.
*/
static inline uint16_t w1_crc16(const uint16_t seed, const uint8_t *src,
const size_t len)
{
return crc16_reflect(W1_CRC16_POLYNOMIAL, seed, src, len);
}
/**
* @}
*/