mgmt/osdp: Add support for OSDP in PD mode of operation
Open Supervised Device Protocol (OSDP) describes the communication protocol for interfacing one or more Peripheral Devices (PD) to a Control Panel (CP). The PDs are slave devices that waits for commands from a CP. The communication happens over a RS485 multi-drop connection with specification for a secure channel communication. This patch adds initial support for OSDP in PD mode without secure channel. Signed-off-by: Siddharth Chandrasekaran <siddharth@embedjournal.com>
This commit is contained in:
committed by
Carles Cufí
parent
27f207694c
commit
9a91b4adf9
225
include/mgmt/osdp.h
Normal file
225
include/mgmt/osdp.h
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _OSDP_H_
|
||||
#define _OSDP_H_
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/slist.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define OSDP_CMD_TEXT_MAX_LEN 32
|
||||
|
||||
/**
|
||||
* @brief Various card formats that a PD can support. This is sent to CP
|
||||
* when a PD must report a card read.
|
||||
*/
|
||||
enum osdp_card_formats_e {
|
||||
OSDP_CARD_FMT_RAW_UNSPECIFIED,
|
||||
OSDP_CARD_FMT_RAW_WIEGAND,
|
||||
OSDP_CARD_FMT_ASCII,
|
||||
OSDP_CARD_FMT_SENTINEL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command sent from CP to Control digital output of PD.
|
||||
*
|
||||
* @param output_no 0 = First Output, 1 = Second Output, etc.
|
||||
* @param control_code One of the following:
|
||||
* 0 - NOP – do not alter this output
|
||||
* 1 - set the permanent state to OFF, abort timed operation (if any)
|
||||
* 2 - set the permanent state to ON, abort timed operation (if any)
|
||||
* 3 - set the permanent state to OFF, allow timed operation to complete
|
||||
* 4 - set the permanent state to ON, allow timed operation to complete
|
||||
* 5 - set the temporary state to ON, resume perm state on timeout
|
||||
* 6 - set the temporary state to OFF, resume permanent state on timeout
|
||||
* @param tmr_count Time in units of 100 ms
|
||||
*/
|
||||
struct osdp_cmd_output {
|
||||
uint8_t output_no;
|
||||
uint8_t control_code;
|
||||
uint16_t tmr_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief LED Colors as specified in OSDP for the on_color/off_color parameters.
|
||||
*/
|
||||
enum osdp_led_color_e {
|
||||
OSDP_LED_COLOR_NONE,
|
||||
OSDP_LED_COLOR_RED,
|
||||
OSDP_LED_COLOR_GREEN,
|
||||
OSDP_LED_COLOR_AMBER,
|
||||
OSDP_LED_COLOR_BLUE,
|
||||
OSDP_LED_COLOR_SENTINEL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief LED params sub-structure. Part of LED command. See struct osdp_cmd_led
|
||||
*
|
||||
* @param control_code One of the following:
|
||||
* Temporary Control Code:
|
||||
* 0 - NOP - do not alter this LED's temporary settings
|
||||
* 1 - Cancel any temporary operation and display this LED's permanent state
|
||||
* immediately
|
||||
* 2 - Set the temporary state as given and start timer immediately
|
||||
* Permanent Control Code:
|
||||
* 0 - NOP - do not alter this LED's permanent settings
|
||||
* 1 - Set the permanent state as given
|
||||
* @param on_count The ON duration of the flash, in units of 100 ms
|
||||
* @param off_count The OFF duration of the flash, in units of 100 ms
|
||||
* @param on_color Color to set during the ON timer (enum osdp_led_color_e)
|
||||
* @param off_color Color to set during the OFF timer (enum osdp_led_color_e)
|
||||
* @param timer Time in units of 100 ms (only for temporary mode)
|
||||
*/
|
||||
struct osdp_cmd_led_params {
|
||||
uint8_t control_code;
|
||||
uint8_t on_count;
|
||||
uint8_t off_count;
|
||||
uint8_t on_color;
|
||||
uint8_t off_color;
|
||||
uint16_t timer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sent from CP to PD to control the behaviour of it's on-board LEDs
|
||||
*
|
||||
* @param reader 0 = First Reader, 1 = Second Reader, etc.
|
||||
* @param led_number 0 = first LED, 1 = second LED, etc.
|
||||
* @param temporary ephemeral LED status descriptor
|
||||
* @param permanent permanent LED status descriptor
|
||||
*/
|
||||
struct osdp_cmd_led {
|
||||
uint8_t reader;
|
||||
uint8_t led_number;
|
||||
struct osdp_cmd_led_params temporary;
|
||||
struct osdp_cmd_led_params permanent;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sent from CP to control the behaviour of a buzzer in the PD.
|
||||
*
|
||||
* @param reader 0 = First Reader, 1 = Second Reader, etc.
|
||||
* @param tone_code 0: no tone, 1: off, 2: default tone, 3+ is TBD.
|
||||
* @param on_count The ON duration of the flash, in units of 100 ms
|
||||
* @param off_count The OFF duration of the flash, in units of 100 ms
|
||||
* @param rep_count The number of times to repeat the ON/OFF cycle; 0: forever
|
||||
*/
|
||||
struct osdp_cmd_buzzer {
|
||||
uint8_t reader;
|
||||
uint8_t tone_code;
|
||||
uint8_t on_count;
|
||||
uint8_t off_count;
|
||||
uint8_t rep_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command to manuplate any display units that the PD supports.
|
||||
*
|
||||
* @param reader 0 = First Reader, 1 = Second Reader, etc.
|
||||
* @param cmd One of the following:
|
||||
* 1 - permanent text, no wrap
|
||||
* 2 - permanent text, with wrap
|
||||
* 3 - temp text, no wrap
|
||||
* 4 - temp text, with wrap
|
||||
* @param temp_time duration to display temporary text, in seconds
|
||||
* @param offset_row row to display the first character (1 indexed)
|
||||
* @param offset_col column to display the first character (1 indexed)
|
||||
* @param length Number of characters in the string
|
||||
* @param data The string to display
|
||||
*/
|
||||
struct osdp_cmd_text {
|
||||
uint8_t reader;
|
||||
uint8_t cmd;
|
||||
uint8_t temp_time;
|
||||
uint8_t offset_row;
|
||||
uint8_t offset_col;
|
||||
uint8_t length;
|
||||
uint8_t data[OSDP_CMD_TEXT_MAX_LEN];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sent in response to a COMSET command. Set communication parameters to
|
||||
* PD. Must be stored in PD non-volatile memory.
|
||||
*
|
||||
* @param addr Unit ID to which this PD will respond after the change takes
|
||||
* effect.
|
||||
* @param baud baud rate value 9600/38400/115200
|
||||
*/
|
||||
struct osdp_cmd_comset {
|
||||
uint8_t addr;
|
||||
uint32_t baud;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This command transfers an encryption key from the CP to a PD.
|
||||
*
|
||||
* @param key_type Type of keys:
|
||||
* - 0x01 – Secure Channel Base Key
|
||||
* @param len Number of bytes of key data - (Key Length in bits + 7) / 8
|
||||
* @param data Key data
|
||||
*/
|
||||
struct osdp_cmd_keyset {
|
||||
uint8_t key_type;
|
||||
uint8_t len;
|
||||
uint8_t data[32];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief OSDP application exposed commands
|
||||
*/
|
||||
enum osdp_cmd_e {
|
||||
OSDP_CMD_OUTPUT = 1,
|
||||
OSDP_CMD_LED,
|
||||
OSDP_CMD_BUZZER,
|
||||
OSDP_CMD_TEXT,
|
||||
OSDP_CMD_KEYSET,
|
||||
OSDP_CMD_COMSET,
|
||||
OSDP_CMD_SENTINEL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief OSDP Command Structure. This is a wrapper for all individual OSDP
|
||||
* commands.
|
||||
*
|
||||
* @param id used to select specific commands in union. Type: enum osdp_cmd_e
|
||||
* @param led LED command structure
|
||||
* @param buzzer buzzer command structure
|
||||
* @param text text command structure
|
||||
* @param output output command structure
|
||||
* @param comset comset command structure
|
||||
* @param keyset keyset command structure
|
||||
*/
|
||||
struct osdp_cmd {
|
||||
sys_snode_t node;
|
||||
int id;
|
||||
union {
|
||||
struct osdp_cmd_led led;
|
||||
struct osdp_cmd_buzzer buzzer;
|
||||
struct osdp_cmd_text text;
|
||||
struct osdp_cmd_output output;
|
||||
struct osdp_cmd_comset comset;
|
||||
struct osdp_cmd_keyset keyset;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param cmd pointer to a command structure that would be filled by the driver.
|
||||
*
|
||||
* @retval 0 on success.
|
||||
* @retval -1 on failure.
|
||||
*/
|
||||
int osdp_pd_get_cmd(struct osdp_cmd *cmd);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _OSDP_H_ */
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
add_subdirectory_ifdef(CONFIG_MCUMGR mcumgr)
|
||||
add_subdirectory_ifdef(CONFIG_UPDATEHUB updatehub)
|
||||
add_subdirectory_ifdef(CONFIG_OSDP osdp)
|
||||
|
||||
@@ -8,4 +8,6 @@ source "subsys/mgmt/mcumgr/Kconfig"
|
||||
|
||||
source "subsys/mgmt/updatehub/Kconfig"
|
||||
|
||||
source "subsys/mgmt/osdp/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
17
subsys/mgmt/osdp/CMakeLists.txt
Normal file
17
subsys/mgmt/osdp/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
#
|
||||
# Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
zephyr_library()
|
||||
|
||||
# PD mode specific sources
|
||||
zephyr_library_sources_ifdef(CONFIG_OSDP_MODE_PD src/osdp_pd.c)
|
||||
|
||||
# Common sources
|
||||
zephyr_library_sources_ifdef(CONFIG_OSDP
|
||||
src/osdp_phy.c
|
||||
src/osdp_common.c
|
||||
src/osdp.c
|
||||
)
|
||||
80
subsys/mgmt/osdp/Kconfig
Normal file
80
subsys/mgmt/osdp/Kconfig
Normal file
@@ -0,0 +1,80 @@
|
||||
#
|
||||
# Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
menuconfig OSDP
|
||||
bool "Open Supervised Device Protocol (OSDP) driver"
|
||||
select RING_BUFFER
|
||||
select SERIAL
|
||||
select SERIAL_HAS_DRIVER
|
||||
select SERIAL_SUPPORT_INTERRUPT
|
||||
select UART_INTERRUPT_DRIVEN
|
||||
select ENTROPY_GENERATOR
|
||||
select TEST_RANDOM_GENERATOR
|
||||
help
|
||||
Add support for Open Supervised Device Protocol (OSDP)
|
||||
|
||||
if OSDP
|
||||
|
||||
# Workaround for not being able to have commas in macro arguments
|
||||
DT_CHOSEN_Z_OSDP_UART := zephyr,osdp-uart
|
||||
|
||||
config OSDP_UART_DEV_NAME
|
||||
string "Device name of UART device for OSDP"
|
||||
default "$(dt_chosen_label,$(DT_CHOSEN_Z_OSDP_UART))" if HAS_DTS
|
||||
default "UART_2"
|
||||
help
|
||||
This option specifies the name of UART device to be used for
|
||||
OSDP
|
||||
|
||||
config OSDP_UART_BAUD_RATE
|
||||
int "OSDP UART baud rate"
|
||||
default 115200
|
||||
help
|
||||
OSDP defines that baud rate can be either 9600 or 38400 or
|
||||
115200.
|
||||
|
||||
config OSDP_LOG_LEVEL
|
||||
int "OSDP Logging Level"
|
||||
default 1
|
||||
help
|
||||
Set the logging level for the OSDP driver
|
||||
|
||||
config OSDP_UART_BUFFER_LENGTH
|
||||
int "OSDP UART buffer length"
|
||||
default 256
|
||||
help
|
||||
OSDP RX and TX buffer FIFO length.
|
||||
|
||||
config OSDP_THREAD_STACK_SIZE
|
||||
int "OSDP Thread stack size"
|
||||
default 512
|
||||
help
|
||||
Thread stack size for osdp refresh thread
|
||||
|
||||
config OSDP_PACKET_TRACE
|
||||
bool "Print bytes sent/received over OSDP to console"
|
||||
help
|
||||
Prints bytes sent/received over OSDP to console for debugging.
|
||||
LOG_HEXDUMP_DBG() is used to achieve this and can be very verbose.
|
||||
|
||||
choice
|
||||
prompt "OSDP Mode of Operation"
|
||||
default OSDP_MODE_PD
|
||||
config OSDP_MODE_CP
|
||||
bool "Configure OSDP in Control Panel mode"
|
||||
help
|
||||
Configure this device to operate as a CP (Control Panel)
|
||||
config OSDP_MODE_PD
|
||||
bool "Configure OSDP in Peripheral Device mode"
|
||||
help
|
||||
Configure this device to operate as a PD (Peripheral Device)
|
||||
endchoice
|
||||
|
||||
if OSDP_MODE_PD
|
||||
source "subsys/mgmt/osdp/Kconfig.pd"
|
||||
endif
|
||||
|
||||
endif # OSDP
|
||||
228
subsys/mgmt/osdp/Kconfig.pd
Normal file
228
subsys/mgmt/osdp/Kconfig.pd
Normal file
@@ -0,0 +1,228 @@
|
||||
#
|
||||
# Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
# OSDP_NUM_CONNECTED_PD is used in CP mode. It is a constant set to 1 when
|
||||
# operating as a PD.
|
||||
config OSDP_NUM_CONNECTED_PD
|
||||
int
|
||||
default 1
|
||||
help
|
||||
In PD mode, number of connected PDs is is always 1 and cannot
|
||||
be configured.
|
||||
|
||||
config OSDP_PD_COMMAND_QUEUE_SIZE
|
||||
int "OSDP Peripheral Device command queue size"
|
||||
default 16
|
||||
help
|
||||
The number of commands that can be queued to a given PD. In CP mode,
|
||||
the queue size is multiplied by number of connected PD so this can grow
|
||||
very quickly.
|
||||
|
||||
config OSDP_PD_ADDRESS
|
||||
int "Peripheral Device Address"
|
||||
default 1
|
||||
range 1 126
|
||||
help
|
||||
The 7 least significant bits represent the address of the PD to which
|
||||
the message is directed, or the address of the PD sending the reply.
|
||||
Address 0x7F is reserved as a broadcast address to which all PDs would
|
||||
respond.
|
||||
|
||||
menu "Peripheral Device ID Information"
|
||||
|
||||
config OSDP_PD_ID_VENDOR_CODE
|
||||
hex "PD Vendor Code"
|
||||
default 0x001A2B3C
|
||||
range 0 0x00FFFFFF
|
||||
help
|
||||
IEEE assigned OUI. Least 24 bits are valid.
|
||||
|
||||
config OSDP_PD_ID_MODEL
|
||||
int "PD Product Model Number"
|
||||
default 1
|
||||
range 0 255
|
||||
help
|
||||
Manufacturer's model number. Least 8 bits are valid.
|
||||
|
||||
config OSDP_PD_ID_VERSION
|
||||
int "PD Product Version"
|
||||
default 1
|
||||
range 0 255
|
||||
help
|
||||
Manufacturer's version of this product. Least 8 bits are valid.
|
||||
|
||||
config OSDP_PD_ID_SERIAL_NUMBER
|
||||
hex "PD Serial Number"
|
||||
default 0xCAFEBABE
|
||||
range 0 0xFFFFFFFF
|
||||
help
|
||||
A 4-byte serial number for the PD.
|
||||
|
||||
config OSDP_PD_ID_FIRMWARE_VERSION
|
||||
hex "PD Firmware Version"
|
||||
default 0x00010100
|
||||
range 0 0x00FFFFFF
|
||||
help
|
||||
Firmware revision code.
|
||||
- Bit 0-7 : build version number;
|
||||
- Bit 8-15 : minor version number;
|
||||
- Bit 16-23: major version number;
|
||||
|
||||
endmenu # "PD ID Information"
|
||||
|
||||
menu "OSDP PD Capabilities"
|
||||
|
||||
menu "Contact Status Monitoring"
|
||||
|
||||
config OSDP_PD_CAP_CONTACT_STATUS_MONITORING_COMP_LEVEL
|
||||
int "Compliance Level"
|
||||
default 0
|
||||
range 0 4
|
||||
help
|
||||
Possible values:
|
||||
- 01: PD monitors and reports the state of the circuit without any
|
||||
supervision. The PD encodes the circuit status per its default
|
||||
interpretation of contact state to active/inactive status.
|
||||
- 02: Like 01, plus: The PD accepts configuration of the encoding of the
|
||||
open/closed circuit status to the reported active/inactive status. (User
|
||||
may configure each circuit as "normally closed" or "normally open".)
|
||||
- 03: Like 02, plus: PD supports supervised monitoring. The operating mode
|
||||
for each circuit is determined by configuration settings.
|
||||
- 04: Like 03, plus: the PD supports custom End-Of-Line settings within
|
||||
the Manufacturer's guidelines.
|
||||
|
||||
config OSDP_PD_CAP_CONTACT_STATUS_MONITORING_NUM_ITEMS
|
||||
int "Number of items"
|
||||
default 0
|
||||
help
|
||||
The number of Inputs
|
||||
|
||||
endmenu # "Contact Status Monitoring"
|
||||
|
||||
menu "Output Control"
|
||||
|
||||
config OSDP_PD_CAP_OUTPUT_CONTROL_COMP_LEVEL
|
||||
int "Compliance Level"
|
||||
default 0
|
||||
range 0 4
|
||||
help
|
||||
Possible values:
|
||||
- 01: The PD is able to activate and deactivate the Output per direct
|
||||
command from the CP.
|
||||
- 02: Like 01, plus: The PD is able to accept configuration of the Output
|
||||
driver to set the inactive state of the Output. The typical state of an
|
||||
inactive Output is the state of the Output when no power is applied to the
|
||||
PD and the output device (relay) is not energized. The inverted drive
|
||||
setting causes the PD to energize the Output during the inactive state and
|
||||
de-energize the Output during the active state. This feature allows the
|
||||
support of "fail-safe/fail-secure" operating modes.
|
||||
- 03: Like 01, plus: The PD is able to accept timed commands to the
|
||||
Output. A timed command specifies the state of the Output for the
|
||||
specified duration.
|
||||
- 04: Like 02 and 03 - normal/inverted drive and timed operation.
|
||||
|
||||
config OSDP_PD_CAP_OUTPUT_CONTROL_NUM_ITEMS
|
||||
int "Number of items"
|
||||
default 0
|
||||
help
|
||||
The number of Outputs.
|
||||
|
||||
endmenu # "Output Control"
|
||||
|
||||
menu "LED Control"
|
||||
|
||||
config OSDP_PD_CAP_READER_LED_CONTROL_COMP_LEVEL
|
||||
int "Compliance Level"
|
||||
default 0
|
||||
range 0 4
|
||||
help
|
||||
Possible values:
|
||||
- 01: the PD support on/off control only
|
||||
- 02: the PD supports timed commands
|
||||
- 03: like 02, plus bi-color LEDs
|
||||
- 04: like 02, plus tri-color LEDs
|
||||
|
||||
config OSDP_PD_CAP_READER_LED_CONTROL_NUM_ITEMS
|
||||
int "Number of items"
|
||||
default 0
|
||||
help
|
||||
The number of LEDs per reader.
|
||||
|
||||
endmenu # "LED Control"
|
||||
|
||||
menu "Audible Output"
|
||||
|
||||
config OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_COMP_LEVEL
|
||||
int "Compliance Level"
|
||||
default 0
|
||||
range 0 2
|
||||
help
|
||||
Possible values:
|
||||
- 01: the PD support on/off control only
|
||||
- 02: the PD supports timed commands
|
||||
|
||||
config OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_NUM_ITEMS
|
||||
int "Number of items"
|
||||
default 0
|
||||
help
|
||||
The number of audible annunciators per reader
|
||||
|
||||
endmenu # "Audible Output"
|
||||
|
||||
menu "Text Output"
|
||||
|
||||
config OSDP_PD_CAP_READER_TEXT_OUTPUT_COMP_LEVEL
|
||||
int "Compliance Level"
|
||||
default 0
|
||||
range 0 4
|
||||
help
|
||||
Possible values:
|
||||
- 00: The PD has no text display support
|
||||
- 01: The PD supports 1 row of 16 characters
|
||||
- 02: the PD supports 2 rows of 16 characters
|
||||
- 03: the PD supports 4 rows of 16 characters
|
||||
- 04: TBD.
|
||||
|
||||
config OSDP_PD_CAP_READER_TEXT_OUTPUT_NUM_ITEMS
|
||||
int "Number of items"
|
||||
default 0
|
||||
help
|
||||
Number of textual displays per reader
|
||||
|
||||
endmenu # "Text Output"
|
||||
|
||||
menu "Card Data Format"
|
||||
|
||||
config OSDP_PD_CAP_CARD_DATA_FORMAT_COMP_LEVEL
|
||||
int "Compliance Level"
|
||||
default 0
|
||||
range 0 3
|
||||
help
|
||||
Possible values:
|
||||
- 01: the PD sends card data to the CP as array of bits, not exceeding
|
||||
1024 bits.
|
||||
- 02: the PD sends card data to the CP as array of BCD characters, not
|
||||
exceeding 256 characters.
|
||||
- 03: the PD can send card data to the CP as array of bits, or as an
|
||||
array of BCD characters.
|
||||
|
||||
endmenu # "Card Data Format"
|
||||
|
||||
menu "Time Keeping"
|
||||
|
||||
config OSDP_PD_CAP_TIME_KEEPING_COMP_LEVEL
|
||||
int "Compliance Level"
|
||||
default 0
|
||||
range 0 2
|
||||
help
|
||||
Possible values:
|
||||
- 00: The PD does not support time/date functionality
|
||||
- 01: The PD understands time/date settings per Command osdp_TDSET
|
||||
- 02: The PD is able to locally update the time and date
|
||||
|
||||
endmenu # "Time Keeping"
|
||||
|
||||
endmenu # "OSDP PD Capabilities"
|
||||
182
subsys/mgmt/osdp/src/osdp.c
Normal file
182
subsys/mgmt/osdp/src/osdp.c
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <kernel.h>
|
||||
#include <init.h>
|
||||
#include <device.h>
|
||||
#include <drivers/uart.h>
|
||||
#include <sys/ring_buffer.h>
|
||||
#include <logging/log.h>
|
||||
|
||||
#include "osdp_common.h"
|
||||
|
||||
LOG_MODULE_REGISTER(osdp, CONFIG_OSDP_LOG_LEVEL);
|
||||
|
||||
struct osdp_device {
|
||||
struct ring_buf rx_buf;
|
||||
struct ring_buf tx_buf;
|
||||
int rx_event_data;
|
||||
struct k_fifo rx_event_fifo;
|
||||
uint8_t rx_fbuf[CONFIG_OSDP_UART_BUFFER_LENGTH];
|
||||
uint8_t tx_fbuf[CONFIG_OSDP_UART_BUFFER_LENGTH];
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static struct osdp osdp_ctx;
|
||||
static struct osdp_cp osdp_cp_ctx;
|
||||
static struct osdp_pd osdp_pd_ctx[CONFIG_OSDP_NUM_CONNECTED_PD];
|
||||
static struct osdp_device osdp_device;
|
||||
static struct k_thread osdp_refresh_thread;
|
||||
static K_THREAD_STACK_DEFINE(osdp_thread_stack, CONFIG_OSDP_THREAD_STACK_SIZE);
|
||||
|
||||
static void osdp_uart_isr(struct device *dev, void *user_data)
|
||||
{
|
||||
uint8_t buf[64];
|
||||
size_t read, wrote, len;
|
||||
struct osdp_device *p = user_data;
|
||||
|
||||
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
|
||||
|
||||
if (uart_irq_rx_ready(dev)) {
|
||||
read = uart_fifo_read(dev, buf, sizeof(buf));
|
||||
if (read) {
|
||||
wrote = ring_buf_put(&p->rx_buf, buf, read);
|
||||
if (wrote < read) {
|
||||
LOG_ERR("RX: Drop %u", read - wrote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
len = ring_buf_get(&p->tx_buf, buf, 1);
|
||||
if (!len) {
|
||||
uart_irq_tx_disable(dev);
|
||||
} else {
|
||||
wrote = uart_fifo_fill(dev, buf, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* wake osdp_refresh thread */
|
||||
k_fifo_put(&p->rx_event_fifo, &p->rx_event_data);
|
||||
}
|
||||
|
||||
static int osdp_uart_receive(void *data, uint8_t *buf, int len)
|
||||
{
|
||||
struct osdp_device *p = data;
|
||||
|
||||
return (int)ring_buf_get(&p->rx_buf, buf, len);
|
||||
}
|
||||
|
||||
static int osdp_uart_send(void *data, uint8_t *buf, int len)
|
||||
{
|
||||
int sent = 0;
|
||||
struct osdp_device *p = data;
|
||||
|
||||
sent = (int)ring_buf_put(&p->tx_buf, buf, len);
|
||||
uart_irq_tx_enable(p->dev);
|
||||
return sent;
|
||||
}
|
||||
|
||||
static void osdp_uart_flush(void *data)
|
||||
{
|
||||
struct osdp_device *p = data;
|
||||
|
||||
ring_buf_reset(&p->tx_buf);
|
||||
ring_buf_reset(&p->rx_buf);
|
||||
}
|
||||
|
||||
struct osdp *osdp_get_ctx()
|
||||
{
|
||||
return &osdp_ctx;
|
||||
}
|
||||
|
||||
static struct osdp *osdp_build_ctx()
|
||||
{
|
||||
int i;
|
||||
struct osdp *ctx;
|
||||
struct osdp_pd *pd;
|
||||
|
||||
ctx = &osdp_ctx;
|
||||
ctx->cp = &osdp_cp_ctx;
|
||||
ctx->cp->__parent = ctx;
|
||||
ctx->cp->num_pd = CONFIG_OSDP_NUM_CONNECTED_PD;
|
||||
ctx->pd = &osdp_pd_ctx[0];
|
||||
SET_CURRENT_PD(ctx, 0);
|
||||
|
||||
for (i = 0; i < CONFIG_OSDP_NUM_CONNECTED_PD; i++) {
|
||||
pd = TO_PD(ctx, i);
|
||||
pd->__parent = ctx;
|
||||
k_mem_slab_init(&pd->cmd.slab,
|
||||
pd->cmd.slab_buf, sizeof(struct osdp_cmd),
|
||||
CONFIG_OSDP_PD_COMMAND_QUEUE_SIZE);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void osdp_refresh(void *arg1, void *arg2, void *arg3)
|
||||
{
|
||||
struct osdp *ctx = osdp_get_ctx();
|
||||
|
||||
while (1) {
|
||||
k_fifo_get(&osdp_device.rx_event_fifo, K_FOREVER);
|
||||
osdp_update(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static int osdp_init(struct device *arg)
|
||||
{
|
||||
ARG_UNUSED(arg);
|
||||
uint8_t c;
|
||||
struct osdp *ctx;
|
||||
struct osdp_device *p = &osdp_device;
|
||||
struct osdp_channel channel = {
|
||||
.send = osdp_uart_send,
|
||||
.recv = osdp_uart_receive,
|
||||
.flush = osdp_uart_flush,
|
||||
.data = &osdp_device,
|
||||
};
|
||||
|
||||
k_fifo_init(&p->rx_event_fifo);
|
||||
|
||||
ring_buf_init(&p->rx_buf, sizeof(p->rx_fbuf), p->rx_fbuf);
|
||||
ring_buf_init(&p->tx_buf, sizeof(p->tx_fbuf), p->tx_fbuf);
|
||||
|
||||
/* init OSDP uart device */
|
||||
p->dev = device_get_binding(CONFIG_OSDP_UART_DEV_NAME);
|
||||
if (p->dev == NULL) {
|
||||
LOG_ERR("Failed to get UART dev binding");
|
||||
return -1;
|
||||
}
|
||||
uart_irq_rx_disable(p->dev);
|
||||
uart_irq_tx_disable(p->dev);
|
||||
uart_irq_callback_user_data_set(p->dev, osdp_uart_isr, p);
|
||||
|
||||
/* Drain UART fifo */
|
||||
while (uart_irq_rx_ready(p->dev)) {
|
||||
uart_fifo_read(p->dev, &c, 1);
|
||||
}
|
||||
|
||||
/* Both TX and RX are interrupt driven */
|
||||
uart_irq_rx_enable(p->dev);
|
||||
|
||||
/* setup OSDP */
|
||||
ctx = osdp_build_ctx();
|
||||
if (osdp_setup(ctx, &channel)) {
|
||||
LOG_ERR("Failed to setup OSDP device!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_INF("OSDP init okay!");
|
||||
|
||||
/* kick off refresh thread */
|
||||
k_thread_create(&osdp_refresh_thread, osdp_thread_stack,
|
||||
CONFIG_OSDP_THREAD_STACK_SIZE, osdp_refresh,
|
||||
NULL, NULL, NULL, K_PRIO_COOP(2), 0, K_NO_WAIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(osdp_init, POST_KERNEL, 10);
|
||||
53
subsys/mgmt/osdp/src/osdp_common.c
Normal file
53
subsys/mgmt/osdp/src/osdp_common.c
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <device.h>
|
||||
#include <sys/crc.h>
|
||||
#include <logging/log.h>
|
||||
|
||||
#include "osdp_common.h"
|
||||
|
||||
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
|
||||
|
||||
void osdp_dump(const char *head, uint8_t *buf, int len)
|
||||
{
|
||||
LOG_HEXDUMP_DBG(buf, len, head);
|
||||
}
|
||||
|
||||
uint16_t osdp_compute_crc16(const uint8_t *buf, size_t len)
|
||||
{
|
||||
return crc16_itu_t(0x1D0F, buf, len);
|
||||
}
|
||||
|
||||
int64_t osdp_millis_now(void)
|
||||
{
|
||||
return (int64_t) k_uptime_get();
|
||||
}
|
||||
|
||||
int64_t osdp_millis_since(int64_t last)
|
||||
{
|
||||
int64_t tmp = last;
|
||||
|
||||
return (int64_t) k_uptime_delta(&tmp);
|
||||
}
|
||||
|
||||
struct osdp_cmd *osdp_cmd_alloc(struct osdp_pd *pd)
|
||||
{
|
||||
struct osdp_cmd *cmd = NULL;
|
||||
|
||||
if (k_mem_slab_alloc(&pd->cmd.slab, (void **)&cmd, K_MSEC(100))) {
|
||||
LOG_ERR("Memory allocation time-out");
|
||||
return NULL;
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void osdp_cmd_free(struct osdp_pd *pd, struct osdp_cmd *cmd)
|
||||
{
|
||||
k_mem_slab_free(&pd->cmd.slab, (void **)&cmd);
|
||||
}
|
||||
442
subsys/mgmt/osdp/src/osdp_common.h
Normal file
442
subsys/mgmt/osdp/src/osdp_common.h
Normal file
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _OSDP_COMMON_H_
|
||||
#define _OSDP_COMMON_H_
|
||||
|
||||
#include <mgmt/osdp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define OSDP_RESP_TOUT_MS (200)
|
||||
|
||||
#define OSDP_CMD_SLAB_BUF_SIZE \
|
||||
(sizeof(struct osdp_cmd) * CONFIG_OSDP_PD_COMMAND_QUEUE_SIZE)
|
||||
|
||||
#define ISSET_FLAG(p, f) (((p)->flags & (f)) == (f))
|
||||
#define SET_FLAG(p, f) ((p)->flags |= (f))
|
||||
#define CLEAR_FLAG(p, f) ((p)->flags &= ~(f))
|
||||
|
||||
#define BYTE_0(x) (uint8_t)(((x) >> 0) & 0xFF)
|
||||
#define BYTE_1(x) (uint8_t)(((x) >> 8) & 0xFF)
|
||||
#define BYTE_2(x) (uint8_t)(((x) >> 16) & 0xFF)
|
||||
#define BYTE_3(x) (uint8_t)(((x) >> 24) & 0xFF)
|
||||
|
||||
/* casting helpers */
|
||||
#define TO_OSDP(p) ((struct osdp *)p)
|
||||
#define TO_CP(p) (((struct osdp *)(p))->cp)
|
||||
#define TO_PD(p, i) (((struct osdp *)(p))->pd + i)
|
||||
#define TO_CTX(p) ((struct osdp *)p->__parent)
|
||||
|
||||
#define GET_CURRENT_PD(p) (TO_CP(p)->current_pd)
|
||||
#define SET_CURRENT_PD(p, i) \
|
||||
do { \
|
||||
TO_CP(p)->current_pd = TO_PD(p, i); \
|
||||
TO_CP(p)->pd_offset = i; \
|
||||
} while (0)
|
||||
#define PD_MASK(ctx) \
|
||||
(uint32_t)((1 << (TO_CP(ctx)->num_pd + 1)) - 1)
|
||||
#define AES_PAD_LEN(x) ((x + 16 - 1) & (~(16 - 1)))
|
||||
#define NUM_PD(ctx) (TO_CP(ctx)->num_pd)
|
||||
#define OSDP_COMMAND_DATA_MAX_LEN sizeof(struct osdp_cmd)
|
||||
|
||||
/**
|
||||
* @brief OSDP reserved commands
|
||||
*/
|
||||
#define CMD_POLL 0x60
|
||||
#define CMD_ID 0x61
|
||||
#define CMD_CAP 0x62
|
||||
#define CMD_DIAG 0x63
|
||||
#define CMD_LSTAT 0x64
|
||||
#define CMD_ISTAT 0x65
|
||||
#define CMD_OSTAT 0x66
|
||||
#define CMD_RSTAT 0x67
|
||||
#define CMD_OUT 0x68
|
||||
#define CMD_LED 0x69
|
||||
#define CMD_BUZ 0x6A
|
||||
#define CMD_TEXT 0x6B
|
||||
#define CMD_RMODE 0x6C
|
||||
#define CMD_TDSET 0x6D
|
||||
#define CMD_COMSET 0x6E
|
||||
#define CMD_DATA 0x6F
|
||||
#define CMD_XMIT 0x70
|
||||
#define CMD_PROMPT 0x71
|
||||
#define CMD_SPE 0x72
|
||||
#define CMD_BIOREAD 0x73
|
||||
#define CMD_BIOMATCH 0x74
|
||||
#define CMD_KEYSET 0x75
|
||||
#define CMD_CHLNG 0x76
|
||||
#define CMD_SCRYPT 0x77
|
||||
#define CMD_CONT 0x79
|
||||
#define CMD_ABORT 0x7A
|
||||
#define CMD_MAXREPLY 0x7B
|
||||
#define CMD_MFG 0x80
|
||||
#define CMD_SCDONE 0xA0
|
||||
#define CMD_XWR 0xA1
|
||||
|
||||
/**
|
||||
* @brief OSDP reserved responses
|
||||
*/
|
||||
#define REPLY_ACK 0x40
|
||||
#define REPLY_NAK 0x41
|
||||
#define REPLY_PDID 0x45
|
||||
#define REPLY_PDCAP 0x46
|
||||
#define REPLY_LSTATR 0x48
|
||||
#define REPLY_ISTATR 0x49
|
||||
#define REPLY_OSTATR 0x4A
|
||||
#define REPLY_RSTATR 0x4B
|
||||
#define REPLY_RAW 0x50
|
||||
#define REPLY_FMT 0x51
|
||||
#define REPLY_PRES 0x52
|
||||
#define REPLY_KEYPPAD 0x53
|
||||
#define REPLY_COM 0x54
|
||||
#define REPLY_SCREP 0x55
|
||||
#define REPLY_SPER 0x56
|
||||
#define REPLY_BIOREADR 0x57
|
||||
#define REPLY_BIOMATCHR 0x58
|
||||
#define REPLY_CCRYPT 0x76
|
||||
#define REPLY_RMAC_I 0x78
|
||||
#define REPLY_MFGREP 0x90
|
||||
#define REPLY_BUSY 0x79
|
||||
#define REPLY_XRD 0xB1
|
||||
|
||||
/**
|
||||
* @brief secure block types
|
||||
*/
|
||||
#define SCS_11 0x11 /* CP -> PD -- CMD_CHLNG */
|
||||
#define SCS_12 0x12 /* PD -> CP -- REPLY_CCRYPT */
|
||||
#define SCS_13 0x13 /* CP -> PD -- CMD_SCRYPT */
|
||||
#define SCS_14 0x14 /* PD -> CP -- REPLY_RMAC_I */
|
||||
|
||||
#define SCS_15 0x15 /* CP -> PD -- packets w MAC w/o ENC */
|
||||
#define SCS_16 0x16 /* PD -> CP -- packets w MAC w/o ENC */
|
||||
#define SCS_17 0x17 /* CP -> PD -- packets w MAC w ENC*/
|
||||
#define SCS_18 0x18 /* PD -> CP -- packets w MAC w ENC*/
|
||||
|
||||
/* PD Flags */
|
||||
#define PD_FLAG_SC_CAPABLE 0x00000001 /* PD secure channel capable */
|
||||
#define PD_FLAG_TAMPER 0x00000002 /* local tamper status */
|
||||
#define PD_FLAG_POWER 0x00000004 /* local power status */
|
||||
#define PD_FLAG_R_TAMPER 0x00000008 /* remote tamper status */
|
||||
#define PD_FLAG_SKIP_SEQ_CHECK 0x00000040 /* disable seq checks (debug) */
|
||||
#define PD_FLAG_SC_USE_SCBKD 0x00000080 /* in this SC attempt, use SCBKD */
|
||||
#define PD_FLAG_SC_ACTIVE 0x00000100 /* secure channel is active */
|
||||
#define PD_FLAG_PD_MODE 0x80000000 /* device is setup as PD */
|
||||
|
||||
enum osdp_pd_nak_code_e {
|
||||
/**
|
||||
* @brief Dummy
|
||||
*/
|
||||
OSDP_PD_NAK_NONE,
|
||||
/**
|
||||
* @brief Message check character(s) error (bad cksum/crc)
|
||||
*/
|
||||
OSDP_PD_NAK_MSG_CHK,
|
||||
/**
|
||||
* @brief Command length error
|
||||
*/
|
||||
OSDP_PD_NAK_CMD_LEN,
|
||||
/**
|
||||
* @brief Unknown Command Code – Command not implemented by PD
|
||||
*/
|
||||
OSDP_PD_NAK_CMD_UNKNOWN,
|
||||
/**
|
||||
* @brief Unexpected sequence number detected in the header
|
||||
*/
|
||||
OSDP_PD_NAK_SEQ_NUM,
|
||||
/**
|
||||
* @brief Unexpected sequence number detected in the header
|
||||
*/
|
||||
OSDP_PD_NAK_SC_UNSUP,
|
||||
/**
|
||||
* @brief unsupported security block or security conditions not met
|
||||
*/
|
||||
OSDP_PD_NAK_SC_COND,
|
||||
/**
|
||||
* @brief BIO_TYPE not supported
|
||||
*/
|
||||
OSDP_PD_NAK_BIO_TYPE,
|
||||
/**
|
||||
* @brief BIO_FORMAT not supported
|
||||
*/
|
||||
OSDP_PD_NAK_BIO_FMT,
|
||||
/**
|
||||
* @brief Unable to process command record
|
||||
*/
|
||||
OSDP_PD_NAK_RECORD,
|
||||
/**
|
||||
* @brief Dummy
|
||||
*/
|
||||
OSDP_PD_NAK_SENTINEL
|
||||
};
|
||||
|
||||
enum osdp_pd_state_e {
|
||||
OSDP_PD_STATE_IDLE,
|
||||
OSDP_PD_STATE_SEND_REPLY,
|
||||
OSDP_PD_STATE_ERR,
|
||||
};
|
||||
|
||||
enum osdp_pkt_errors_e {
|
||||
OSDP_ERR_PKT_FMT = -1,
|
||||
OSDP_ERR_PKT_WAIT = -2,
|
||||
OSDP_ERR_PKT_SKIP = -3
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Various PD capability function codes.
|
||||
*/
|
||||
enum osdp_pd_cap_function_code_e {
|
||||
/**
|
||||
* @brief Dummy.
|
||||
*/
|
||||
OSDP_PD_CAP_UNUSED,
|
||||
|
||||
/**
|
||||
* @brief This function indicates the ability to monitor the status of a
|
||||
* switch using a two-wire electrical connection between the PD and the
|
||||
* switch. The on/off position of the switch indicates the state of an
|
||||
* external device.
|
||||
*
|
||||
* The PD may simply resolve all circuit states to an open/closed
|
||||
* status, or it may implement supervision of the monitoring circuit.
|
||||
* A supervised circuit is able to indicate circuit fault status in
|
||||
* addition to open/closed status.
|
||||
*/
|
||||
OSDP_PD_CAP_CONTACT_STATUS_MONITORING,
|
||||
|
||||
/**
|
||||
* @brief This function provides a switched output, typically in the
|
||||
* form of a relay. The Output has two states: active or inactive. The
|
||||
* Control Panel (CP) can directly set the Output's state, or, if the PD
|
||||
* supports timed operations, the CP can specify a time period for the
|
||||
* activation of the Output.
|
||||
*/
|
||||
OSDP_PD_CAP_OUTPUT_CONTROL,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the form of the card data is
|
||||
* presented to the Control Panel.
|
||||
*/
|
||||
OSDP_PD_CAP_CARD_DATA_FORMAT,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the presence of and type of LEDs.
|
||||
*/
|
||||
OSDP_PD_CAP_READER_LED_CONTROL,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the presence of and type of an
|
||||
* Audible Annunciator (buzzer or similar tone generator)
|
||||
*/
|
||||
OSDP_PD_CAP_READER_AUDIBLE_OUTPUT,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates that the PD supports a text display
|
||||
* emulating character-based display terminals.
|
||||
*/
|
||||
OSDP_PD_CAP_READER_TEXT_OUTPUT,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates that the type of date and time
|
||||
* awareness or time keeping ability of the PD.
|
||||
*/
|
||||
OSDP_PD_CAP_TIME_KEEPING,
|
||||
|
||||
/**
|
||||
* @brief All PDs must be able to support the checksum mode. This
|
||||
* capability indicates if the PD is capable of supporting CRC mode.
|
||||
*/
|
||||
OSDP_PD_CAP_CHECK_CHARACTER_SUPPORT,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the extent to which the PD supports
|
||||
* communication security (Secure Channel Communication)
|
||||
*/
|
||||
OSDP_PD_CAP_COMMUNICATION_SECURITY,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the maximum size single message the
|
||||
* PD can receive.
|
||||
*/
|
||||
OSDP_PD_CAP_RECEIVE_BUFFERSIZE,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the maximum size multi-part message
|
||||
* which the PD can handle.
|
||||
*/
|
||||
OSDP_PD_CAP_LARGEST_COMBINED_MESSAGE_SIZE,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates whether the PD supports the
|
||||
* transparent mode used for communicating directly with a smart card.
|
||||
*/
|
||||
OSDP_PD_CAP_SMART_CARD_SUPPORT,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the number of credential reader
|
||||
* devices present. Compliance levels are bit fields to be assigned as
|
||||
* needed.
|
||||
*/
|
||||
OSDP_PD_CAP_READERS,
|
||||
|
||||
/**
|
||||
* @brief This capability indicates the ability of the reader to handle
|
||||
* biometric input
|
||||
*/
|
||||
OSDP_PD_CAP_BIOMETRICS,
|
||||
|
||||
/**
|
||||
* @brief Capability Sentinel
|
||||
*/
|
||||
OSDP_PD_CAP_SENTINEL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PD capability structure. Each PD capability has a 3 byte
|
||||
* representation.
|
||||
*
|
||||
* @param function_code One of enum osdp_pd_cap_function_code_e.
|
||||
* @param compliance_level A function_code dependent number that indicates what
|
||||
* the PD can do with this capability.
|
||||
* @param num_items Number of such capability entities in PD.
|
||||
*/
|
||||
struct osdp_pd_cap {
|
||||
uint8_t function_code;
|
||||
uint8_t compliance_level;
|
||||
uint8_t num_items;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PD ID information advertised by the PD.
|
||||
*
|
||||
* @param version 3-bytes IEEE assigned OUI
|
||||
* @param model 1-byte Manufacturer's model number
|
||||
* @param vendor_code 1-Byte Manufacturer's version number
|
||||
* @param serial_number 4-byte serial number for the PD
|
||||
* @param firmware_version 3-byte version (major, minor, build)
|
||||
*/
|
||||
struct osdp_pd_id {
|
||||
int version;
|
||||
int model;
|
||||
uint32_t vendor_code;
|
||||
uint32_t serial_number;
|
||||
uint32_t firmware_version;
|
||||
};
|
||||
|
||||
struct osdp_channel {
|
||||
/**
|
||||
* @brief pointer to a block of memory that will be passed to the
|
||||
* send/receive method. This is optional and can be left empty.
|
||||
*/
|
||||
void *data;
|
||||
|
||||
/**
|
||||
* @brief pointer to function that copies received bytes into buffer
|
||||
* @param data for use by underlying layers. channel_s::data is passed
|
||||
* @param buf byte array copy incoming data
|
||||
* @param len sizeof `buf`. Can copy utmost `len` bytes into `buf`
|
||||
*
|
||||
* @retval +ve: number of bytes copied on to `bug`. Must be <= `len`
|
||||
* @retval -ve on errors
|
||||
*/
|
||||
int (*recv)(void *data, uint8_t *buf, int maxlen);
|
||||
|
||||
/**
|
||||
* @brief pointer to function that sends byte array into some channel
|
||||
* @param data for use by underlying layers. channel_s::data is passed
|
||||
* @param buf byte array to be sent
|
||||
* @param len number of bytes in `buf`
|
||||
*
|
||||
* @retval +ve: number of bytes sent. must be <= `len`
|
||||
* @retval -ve on errors
|
||||
*/
|
||||
int (*send)(void *data, uint8_t *buf, int len);
|
||||
|
||||
/**
|
||||
* @brief pointer to function that drops all bytes in TX/RX fifo
|
||||
* @param data for use by underlying layers. channel_s::data is passed
|
||||
*/
|
||||
void (*flush)(void *data);
|
||||
};
|
||||
|
||||
struct osdp_cmd_queue {
|
||||
sys_slist_t queue;
|
||||
struct k_mem_slab slab;
|
||||
uint8_t slab_buf[OSDP_CMD_SLAB_BUF_SIZE];
|
||||
};
|
||||
|
||||
struct osdp_pd {
|
||||
void *__parent;
|
||||
int offset;
|
||||
uint32_t flags;
|
||||
|
||||
/* OSDP specified data */
|
||||
int baud_rate;
|
||||
int address;
|
||||
int seq_number;
|
||||
struct osdp_pd_cap cap[OSDP_PD_CAP_SENTINEL];
|
||||
struct osdp_pd_id id;
|
||||
|
||||
/* PD state management */
|
||||
enum osdp_pd_state_e pd_state;
|
||||
int64_t tstamp;
|
||||
int64_t sc_tstamp;
|
||||
int phy_state;
|
||||
uint8_t rx_buf[CONFIG_OSDP_UART_BUFFER_LENGTH];
|
||||
int rx_buf_len;
|
||||
int64_t phy_tstamp;
|
||||
|
||||
int cmd_id;
|
||||
int reply_id;
|
||||
uint8_t cmd_data[OSDP_COMMAND_DATA_MAX_LEN];
|
||||
|
||||
struct osdp_channel channel;
|
||||
struct osdp_cmd_queue cmd;
|
||||
};
|
||||
|
||||
struct osdp_cp {
|
||||
void *__parent;
|
||||
uint32_t flags;
|
||||
|
||||
int num_pd;
|
||||
|
||||
struct osdp_pd *current_pd; /* current operational pd's pointer */
|
||||
int pd_offset; /* current pd's offset into ctx->pd */
|
||||
};
|
||||
|
||||
struct osdp {
|
||||
int magic;
|
||||
uint32_t flags;
|
||||
|
||||
uint8_t sc_master_key[16];
|
||||
struct osdp_cp *cp;
|
||||
struct osdp_pd *pd;
|
||||
};
|
||||
|
||||
/* from osdp_phy.c */
|
||||
int osdp_phy_packet_init(struct osdp_pd *p, uint8_t *buf, int max_len);
|
||||
int osdp_phy_packet_finalize(struct osdp_pd *p, uint8_t *buf,
|
||||
int len, int max_len);
|
||||
int osdp_phy_decode_packet(struct osdp_pd *p, uint8_t *buf, int len);
|
||||
void osdp_phy_state_reset(struct osdp_pd *pd);
|
||||
int osdp_phy_packet_get_data_offset(struct osdp_pd *p, const uint8_t *buf);
|
||||
uint8_t *osdp_phy_packet_get_smb(struct osdp_pd *p, const uint8_t *buf);
|
||||
|
||||
/* from osdp_common.c */
|
||||
int64_t osdp_millis_now(void);
|
||||
int64_t osdp_millis_since(int64_t last);
|
||||
void osdp_dump(const char *head, uint8_t *buf, int len);
|
||||
uint16_t osdp_compute_crc16(const uint8_t *buf, size_t len);
|
||||
struct osdp_cmd *osdp_cmd_alloc(struct osdp_pd *pd);
|
||||
void osdp_cmd_free(struct osdp_pd *pd, struct osdp_cmd *cmd);
|
||||
|
||||
/* from osdp.c */
|
||||
struct osdp *osdp_get_ctx();
|
||||
|
||||
/* must be implemented by CP or PD */
|
||||
int osdp_setup(struct osdp *ctx, struct osdp_channel *channel);
|
||||
void osdp_update(struct osdp *ctx);
|
||||
|
||||
#endif /* _OSDP_COMMON_H_ */
|
||||
662
subsys/mgmt/osdp/src/osdp_pd.c
Normal file
662
subsys/mgmt/osdp/src/osdp_pd.c
Normal file
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "osdp_common.h"
|
||||
|
||||
#define TAG "PD: "
|
||||
|
||||
#define CMD_POLL_DATA_LEN 0
|
||||
#define CMD_LSTAT_DATA_LEN 0
|
||||
#define CMD_ISTAT_DATA_LEN 0
|
||||
#define CMD_OSTAT_DATA_LEN 0
|
||||
#define CMD_RSTAT_DATA_LEN 0
|
||||
#define CMD_ID_DATA_LEN 1
|
||||
#define CMD_CAP_DATA_LEN 1
|
||||
#define CMD_OUT_DATA_LEN 4
|
||||
#define CMD_LED_DATA_LEN 14
|
||||
#define CMD_BUZ_DATA_LEN 5
|
||||
#define CMD_TEXT_DATA_LEN 6 /* variable length command */
|
||||
#define CMD_COMSET_DATA_LEN 5
|
||||
|
||||
#define REPLY_ACK_LEN 1
|
||||
#define REPLY_PDID_LEN 13
|
||||
#define REPLY_PDCAP_LEN 1 /* variable length command */
|
||||
#define REPLY_PDCAP_ENTITY_LEN 3
|
||||
#define REPLY_LSTATR_LEN 3
|
||||
#define REPLY_RSTATR_LEN 2
|
||||
#define REPLY_COM_LEN 6
|
||||
#define REPLY_NAK_LEN 2
|
||||
|
||||
static struct osdp_pd_id osdp_pd_id = {
|
||||
.version = CONFIG_OSDP_PD_ID_VERSION,
|
||||
.model = CONFIG_OSDP_PD_ID_MODEL,
|
||||
.vendor_code = CONFIG_OSDP_PD_ID_VENDOR_CODE,
|
||||
.serial_number = CONFIG_OSDP_PD_ID_SERIAL_NUMBER,
|
||||
.firmware_version = CONFIG_OSDP_PD_ID_FIRMWARE_VERSION,
|
||||
};
|
||||
|
||||
static struct osdp_pd_cap osdp_pd_cap[] = {
|
||||
/* Driver Implicit cababilities */
|
||||
{
|
||||
OSDP_PD_CAP_CHECK_CHARACTER_SUPPORT,
|
||||
1, /* The PD supports the 16-bit CRC-16 mode */
|
||||
0, /* N/A */
|
||||
},
|
||||
|
||||
/* Configured from Kconfig */
|
||||
{
|
||||
OSDP_PD_CAP_CONTACT_STATUS_MONITORING,
|
||||
CONFIG_OSDP_PD_CAP_CONTACT_STATUS_MONITORING_COMP_LEVEL,
|
||||
CONFIG_OSDP_PD_CAP_CONTACT_STATUS_MONITORING_NUM_ITEMS,
|
||||
},
|
||||
{
|
||||
OSDP_PD_CAP_OUTPUT_CONTROL,
|
||||
CONFIG_OSDP_PD_CAP_OUTPUT_CONTROL_COMP_LEVEL,
|
||||
CONFIG_OSDP_PD_CAP_OUTPUT_CONTROL_NUM_ITEMS,
|
||||
},
|
||||
{
|
||||
OSDP_PD_CAP_READER_LED_CONTROL,
|
||||
CONFIG_OSDP_PD_CAP_READER_LED_CONTROL_COMP_LEVEL,
|
||||
CONFIG_OSDP_PD_CAP_READER_LED_CONTROL_NUM_ITEMS,
|
||||
},
|
||||
{
|
||||
OSDP_PD_CAP_READER_AUDIBLE_OUTPUT,
|
||||
CONFIG_OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_COMP_LEVEL,
|
||||
CONFIG_OSDP_PD_CAP_READER_AUDIBLE_OUTPUT_NUM_ITEMS,
|
||||
},
|
||||
{
|
||||
OSDP_PD_CAP_READER_TEXT_OUTPUT,
|
||||
CONFIG_OSDP_PD_CAP_READER_TEXT_OUTPUT_COMP_LEVEL,
|
||||
CONFIG_OSDP_PD_CAP_READER_TEXT_OUTPUT_NUM_ITEMS,
|
||||
},
|
||||
{
|
||||
OSDP_PD_CAP_CARD_DATA_FORMAT,
|
||||
CONFIG_OSDP_PD_CAP_CARD_DATA_FORMAT_COMP_LEVEL,
|
||||
0, /* N/A set to 0 */
|
||||
},
|
||||
{
|
||||
OSDP_PD_CAP_TIME_KEEPING,
|
||||
CONFIG_OSDP_PD_CAP_TIME_KEEPING_COMP_LEVEL,
|
||||
0, /* N/A set to 0 */
|
||||
},
|
||||
{ -1, 0, 0 } /* Sentinel */
|
||||
};
|
||||
|
||||
static void pd_enqueue_command(struct osdp_pd *pd, struct osdp_cmd *cmd)
|
||||
{
|
||||
sys_slist_append(&pd->cmd.queue, &cmd->node);
|
||||
}
|
||||
|
||||
static struct osdp_cmd *pd_get_last_command(struct osdp_pd *pd)
|
||||
{
|
||||
return (struct osdp_cmd *)sys_slist_peek_tail(&pd->cmd.queue);
|
||||
}
|
||||
|
||||
static void pd_decode_command(struct osdp_pd *pd, uint8_t *buf, int len)
|
||||
{
|
||||
int i, ret = -1, pos = 0;
|
||||
struct osdp_cmd *cmd;
|
||||
|
||||
pd->reply_id = 0;
|
||||
pd->cmd_id = buf[pos++];
|
||||
len--;
|
||||
|
||||
switch (pd->cmd_id) {
|
||||
case CMD_POLL:
|
||||
if (len != CMD_POLL_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
pd->reply_id = REPLY_ACK;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_LSTAT:
|
||||
if (len != CMD_LSTAT_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
pd->reply_id = REPLY_LSTATR;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_ISTAT:
|
||||
if (len != CMD_ISTAT_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
pd->reply_id = REPLY_ISTATR;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_OSTAT:
|
||||
if (len != CMD_OSTAT_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
pd->reply_id = REPLY_OSTATR;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_RSTAT:
|
||||
if (len != CMD_RSTAT_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
pd->reply_id = REPLY_RSTATR;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_ID:
|
||||
if (len != CMD_ID_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
pos++; /* Skip reply type info. */
|
||||
pd->reply_id = REPLY_PDID;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_CAP:
|
||||
if (len != CMD_CAP_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
pos++; /* Skip reply type info. */
|
||||
pd->reply_id = REPLY_PDCAP;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_OUT:
|
||||
if (len != CMD_OUT_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
cmd = osdp_cmd_alloc(pd);
|
||||
if (cmd == NULL) {
|
||||
LOG_ERR(TAG "cmd alloc error");
|
||||
break;
|
||||
}
|
||||
cmd->id = OSDP_CMD_OUTPUT;
|
||||
cmd->output.output_no = buf[pos++];
|
||||
cmd->output.control_code = buf[pos++];
|
||||
cmd->output.tmr_count = buf[pos++];
|
||||
cmd->output.tmr_count |= buf[pos++] << 8;
|
||||
pd_enqueue_command(pd, cmd);
|
||||
pd->reply_id = REPLY_ACK;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_LED:
|
||||
if (len != CMD_LED_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
cmd = osdp_cmd_alloc(pd);
|
||||
if (cmd == NULL) {
|
||||
LOG_ERR(TAG "cmd alloc error");
|
||||
break;
|
||||
}
|
||||
cmd->id = OSDP_CMD_LED;
|
||||
cmd->led.reader = buf[pos++];
|
||||
cmd->led.led_number = buf[pos++];
|
||||
|
||||
cmd->led.temporary.control_code = buf[pos++];
|
||||
cmd->led.temporary.on_count = buf[pos++];
|
||||
cmd->led.temporary.off_count = buf[pos++];
|
||||
cmd->led.temporary.on_color = buf[pos++];
|
||||
cmd->led.temporary.off_color = buf[pos++];
|
||||
cmd->led.temporary.timer = buf[pos++];
|
||||
cmd->led.temporary.timer |= buf[pos++] << 8;
|
||||
|
||||
cmd->led.permanent.control_code = buf[pos++];
|
||||
cmd->led.permanent.on_count = buf[pos++];
|
||||
cmd->led.permanent.off_count = buf[pos++];
|
||||
cmd->led.permanent.on_color = buf[pos++];
|
||||
cmd->led.permanent.off_color = buf[pos++];
|
||||
pd_enqueue_command(pd, cmd);
|
||||
pd->reply_id = REPLY_ACK;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_BUZ:
|
||||
if (len != CMD_BUZ_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
cmd = osdp_cmd_alloc(pd);
|
||||
if (cmd == NULL) {
|
||||
LOG_ERR(TAG "cmd alloc error");
|
||||
break;
|
||||
}
|
||||
cmd->id = OSDP_CMD_BUZZER;
|
||||
cmd->buzzer.reader = buf[pos++];
|
||||
cmd->buzzer.tone_code = buf[pos++];
|
||||
cmd->buzzer.on_count = buf[pos++];
|
||||
cmd->buzzer.off_count = buf[pos++];
|
||||
cmd->buzzer.rep_count = buf[pos++];
|
||||
pd_enqueue_command(pd, cmd);
|
||||
pd->reply_id = REPLY_ACK;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_TEXT:
|
||||
if (len < CMD_TEXT_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
cmd = osdp_cmd_alloc(pd);
|
||||
if (cmd == NULL) {
|
||||
LOG_ERR(TAG "cmd alloc error");
|
||||
break;
|
||||
}
|
||||
cmd->id = OSDP_CMD_TEXT;
|
||||
cmd->text.reader = buf[pos++];
|
||||
cmd->text.cmd = buf[pos++];
|
||||
cmd->text.temp_time = buf[pos++];
|
||||
cmd->text.offset_row = buf[pos++];
|
||||
cmd->text.offset_col = buf[pos++];
|
||||
cmd->text.length = buf[pos++];
|
||||
if (cmd->text.length > OSDP_CMD_TEXT_MAX_LEN ||
|
||||
((len - CMD_TEXT_DATA_LEN) < cmd->text.length) ||
|
||||
cmd->text.length > OSDP_CMD_TEXT_MAX_LEN) {
|
||||
osdp_cmd_free(pd, cmd);
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < cmd->text.length; i++) {
|
||||
cmd->text.data[i] = buf[pos++];
|
||||
}
|
||||
pd_enqueue_command(pd, cmd);
|
||||
pd->reply_id = REPLY_ACK;
|
||||
ret = 0;
|
||||
break;
|
||||
case CMD_COMSET:
|
||||
if (len != CMD_COMSET_DATA_LEN) {
|
||||
break;
|
||||
}
|
||||
cmd = osdp_cmd_alloc(pd);
|
||||
if (cmd == NULL) {
|
||||
LOG_ERR(TAG "cmd alloc error");
|
||||
break;
|
||||
}
|
||||
cmd->id = OSDP_CMD_COMSET;
|
||||
cmd->comset.addr = buf[pos++];
|
||||
cmd->comset.baud = buf[pos++];
|
||||
cmd->comset.baud |= buf[pos++] << 8;
|
||||
cmd->comset.baud |= buf[pos++] << 16;
|
||||
cmd->comset.baud |= buf[pos++] << 24;
|
||||
if (cmd->comset.addr >= 0x7F || (cmd->comset.baud != 9600 &&
|
||||
cmd->comset.baud != 38400 && cmd->comset.baud != 115200)) {
|
||||
LOG_ERR("COMSET Failed! command discarded.");
|
||||
cmd->comset.addr = pd->address;
|
||||
cmd->comset.baud = pd->baud_rate;
|
||||
}
|
||||
pd_enqueue_command(pd, cmd);
|
||||
pd->reply_id = REPLY_COM;
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
pd->reply_id = REPLY_NAK;
|
||||
pd->cmd_data[0] = OSDP_PD_NAK_CMD_UNKNOWN;
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
LOG_ERR(TAG "Invalid command structure. CMD: %02x, Len: %d",
|
||||
pd->cmd_id, len);
|
||||
pd->reply_id = REPLY_NAK;
|
||||
pd->cmd_data[0] = OSDP_PD_NAK_CMD_LEN;
|
||||
}
|
||||
|
||||
if (pd->cmd_id != CMD_POLL) {
|
||||
LOG_DBG(TAG "CMD: %02x REPLY: %02x", pd->cmd_id, pd->reply_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns:
|
||||
* +ve: length of command
|
||||
* -ve: error
|
||||
*/
|
||||
static int pd_build_reply(struct osdp_pd *pd, uint8_t *buf, int max_len)
|
||||
{
|
||||
int i, data_off, len = 0, ret = -1;
|
||||
struct osdp_cmd *cmd;
|
||||
|
||||
data_off = osdp_phy_packet_get_data_offset(pd, buf);
|
||||
buf += data_off;
|
||||
max_len -= data_off;
|
||||
if (max_len <= 0) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (pd->reply_id) {
|
||||
case REPLY_ACK:
|
||||
if (max_len < REPLY_ACK_LEN) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
break;
|
||||
}
|
||||
buf[len++] = pd->reply_id;
|
||||
ret = 0;
|
||||
break;
|
||||
case REPLY_PDID:
|
||||
if (max_len < REPLY_PDID_LEN) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
break;
|
||||
}
|
||||
buf[len++] = pd->reply_id;
|
||||
|
||||
buf[len++] = BYTE_0(pd->id.vendor_code);
|
||||
buf[len++] = BYTE_1(pd->id.vendor_code);
|
||||
buf[len++] = BYTE_2(pd->id.vendor_code);
|
||||
|
||||
buf[len++] = pd->id.model;
|
||||
buf[len++] = pd->id.version;
|
||||
|
||||
buf[len++] = BYTE_0(pd->id.serial_number);
|
||||
buf[len++] = BYTE_1(pd->id.serial_number);
|
||||
buf[len++] = BYTE_2(pd->id.serial_number);
|
||||
buf[len++] = BYTE_3(pd->id.serial_number);
|
||||
|
||||
buf[len++] = BYTE_3(pd->id.firmware_version);
|
||||
buf[len++] = BYTE_2(pd->id.firmware_version);
|
||||
buf[len++] = BYTE_1(pd->id.firmware_version);
|
||||
ret = 0;
|
||||
break;
|
||||
case REPLY_PDCAP:
|
||||
if (max_len < REPLY_PDCAP_LEN) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
break;
|
||||
}
|
||||
buf[len++] = pd->reply_id;
|
||||
for (i = 0; i < OSDP_PD_CAP_SENTINEL; i++) {
|
||||
if (pd->cap[i].function_code != i) {
|
||||
continue;
|
||||
}
|
||||
if (max_len < REPLY_PDCAP_ENTITY_LEN) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
break;
|
||||
}
|
||||
buf[len++] = i;
|
||||
buf[len++] = pd->cap[i].compliance_level;
|
||||
buf[len++] = pd->cap[i].num_items;
|
||||
max_len -= REPLY_PDCAP_ENTITY_LEN;
|
||||
}
|
||||
ret = 0;
|
||||
break;
|
||||
case REPLY_LSTATR:
|
||||
if (max_len < REPLY_LSTATR_LEN) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
break;
|
||||
}
|
||||
buf[len++] = pd->reply_id;
|
||||
buf[len++] = ISSET_FLAG(pd, PD_FLAG_TAMPER);
|
||||
buf[len++] = ISSET_FLAG(pd, PD_FLAG_POWER);
|
||||
ret = 0;
|
||||
break;
|
||||
case REPLY_RSTATR:
|
||||
if (max_len < REPLY_RSTATR_LEN) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
break;
|
||||
}
|
||||
buf[len++] = pd->reply_id;
|
||||
buf[len++] = ISSET_FLAG(pd, PD_FLAG_R_TAMPER);
|
||||
ret = 0;
|
||||
break;
|
||||
case REPLY_COM:
|
||||
if (max_len < REPLY_COM_LEN) {
|
||||
LOG_ERR(TAG "Out of buffer space!");
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* If COMSET succeeds, the PD must reply with the old params and
|
||||
* then switch to the new params from then then on. We have the
|
||||
* new params in the commands struct that we just enqueued so
|
||||
* we can peek at tail of command queue and set that to
|
||||
* pd->addr/pd->baud_rate.
|
||||
*
|
||||
* TODO: Persist pd->address and pd->baud_rate via
|
||||
* subsys/settings
|
||||
*/
|
||||
cmd = pd_get_last_command(pd);
|
||||
if (cmd == NULL || cmd->id != OSDP_CMD_COMSET) {
|
||||
LOG_ERR(TAG "Failed to fetch queue tail for COMSET");
|
||||
break;
|
||||
}
|
||||
|
||||
buf[len++] = pd->reply_id;
|
||||
buf[len++] = cmd->comset.addr;
|
||||
buf[len++] = BYTE_0(cmd->comset.baud);
|
||||
buf[len++] = BYTE_1(cmd->comset.baud);
|
||||
buf[len++] = BYTE_2(cmd->comset.baud);
|
||||
buf[len++] = BYTE_3(cmd->comset.baud);
|
||||
|
||||
pd->address = (int)cmd->comset.addr;
|
||||
pd->baud_rate = (int)cmd->comset.baud;
|
||||
LOG_INF("COMSET Succeeded! New PD-Addr: %d; Baud: %d",
|
||||
pd->address, pd->baud_rate);
|
||||
ret = 0;
|
||||
break;
|
||||
case REPLY_NAK:
|
||||
if (max_len < REPLY_NAK_LEN) {
|
||||
LOG_ERR(TAG "Fatal: insufficent space for sending NAK");
|
||||
return -1;
|
||||
}
|
||||
buf[len++] = pd->reply_id;
|
||||
buf[len++] = pd->cmd_data[0];
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
/* catch all errors and report it as a RECORD error to CP */
|
||||
LOG_ERR(TAG "ReplyID unknown or insufficent space or some other"
|
||||
"error. Sending NAK");
|
||||
if (max_len < REPLY_NAK_LEN) {
|
||||
LOG_ERR(TAG "Fatal: insufficent space for sending NAK");
|
||||
return -1;
|
||||
}
|
||||
buf[0] = REPLY_NAK;
|
||||
buf[1] = OSDP_PD_NAK_RECORD;
|
||||
len = 2;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* pd_send_reply - blocking send; doesn't handle partials
|
||||
* Returns:
|
||||
* 0 - success
|
||||
* -1 - failure
|
||||
*/
|
||||
static int pd_send_reply(struct osdp_pd *pd)
|
||||
{
|
||||
int ret, len;
|
||||
|
||||
/* init packet buf with header */
|
||||
len = osdp_phy_packet_init(pd, pd->rx_buf, sizeof(pd->rx_buf));
|
||||
if (len < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* fill reply data */
|
||||
ret = pd_build_reply(pd, pd->rx_buf, sizeof(pd->rx_buf));
|
||||
if (ret <= 0) {
|
||||
return -1;
|
||||
}
|
||||
len += ret;
|
||||
|
||||
/* finalize packet */
|
||||
len = osdp_phy_packet_finalize(pd, pd->rx_buf, len, sizeof(pd->rx_buf));
|
||||
if (len < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = pd->channel.send(pd->channel.data, pd->rx_buf, len);
|
||||
|
||||
if (IS_ENABLED(CONFIG_OSDP_PACKET_TRACE)) {
|
||||
if (pd->cmd_id != CMD_POLL) {
|
||||
osdp_dump("PD sent", pd->rx_buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
return (ret == len) ? 0 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* pd_receve_packet - received buffer from serial stream handling partials
|
||||
* Returns:
|
||||
* 0: success
|
||||
* 1: no data yet
|
||||
* -1: fatal errors
|
||||
* -2: phy layer errors that need to reply with NAK
|
||||
*/
|
||||
static int pd_receve_packet(struct osdp_pd *pd)
|
||||
{
|
||||
uint8_t *buf;
|
||||
int rec_bytes, ret, was_empty, max_len;
|
||||
|
||||
was_empty = pd->rx_buf_len == 0;
|
||||
buf = pd->rx_buf + pd->rx_buf_len;
|
||||
max_len = sizeof(pd->rx_buf) - pd->rx_buf_len;
|
||||
|
||||
rec_bytes = pd->channel.recv(pd->channel.data, buf, max_len);
|
||||
if (rec_bytes <= 0) {
|
||||
return 1;
|
||||
}
|
||||
if (was_empty && rec_bytes > 0) {
|
||||
/* Start of message */
|
||||
pd->tstamp = osdp_millis_now();
|
||||
}
|
||||
pd->rx_buf_len += rec_bytes;
|
||||
|
||||
if (IS_ENABLED(CONFIG_OSDP_PACKET_TRACE)) {
|
||||
/**
|
||||
* A crude way of identifying and not printing poll messages
|
||||
* when CONFIG_OSDP_PACKET_TRACE is enabled. This is an early
|
||||
* print to catch errors so keeping it simple.
|
||||
*/
|
||||
if (pd->rx_buf_len > 8 &&
|
||||
pd->rx_buf[6] != CMD_POLL && pd->rx_buf[8] != CMD_POLL) {
|
||||
osdp_dump("PD received", pd->rx_buf, pd->rx_buf_len);
|
||||
}
|
||||
}
|
||||
|
||||
pd->reply_id = 0; /* reset past reply ID so phy can send NAK */
|
||||
pd->cmd_data[0] = 0; /* reset past NAK reason */
|
||||
ret = osdp_phy_decode_packet(pd, pd->rx_buf, pd->rx_buf_len);
|
||||
if (ret == OSDP_ERR_PKT_FMT) {
|
||||
if (pd->reply_id != 0) {
|
||||
return -2; /* Send a NAK */
|
||||
}
|
||||
return -1; /* fatal errors */
|
||||
} else if (ret == OSDP_ERR_PKT_WAIT) {
|
||||
/* rx_buf_len != pkt->len; wait for more data */
|
||||
return 1;
|
||||
} else if (ret == OSDP_ERR_PKT_SKIP) {
|
||||
/* soft fail - discard this message */
|
||||
pd->rx_buf_len = 0;
|
||||
if (pd->channel.flush) {
|
||||
pd->channel.flush(pd->channel.data);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
pd->rx_buf_len = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osdp_update(struct osdp *ctx)
|
||||
{
|
||||
int ret;
|
||||
struct osdp_pd *pd = TO_PD(ctx, 0);
|
||||
|
||||
switch (pd->pd_state) {
|
||||
case OSDP_PD_STATE_IDLE:
|
||||
ret = pd_receve_packet(pd);
|
||||
if (ret == 1) {
|
||||
break;
|
||||
}
|
||||
if (ret == -1 || (pd->rx_buf_len > 0 &&
|
||||
osdp_millis_since(pd->tstamp) > OSDP_RESP_TOUT_MS)) {
|
||||
/**
|
||||
* When we receive a command from PD after a timeout,
|
||||
* any established secure channel must be discarded.
|
||||
*/
|
||||
LOG_ERR(TAG "receive errors/timeout");
|
||||
pd->pd_state = OSDP_PD_STATE_ERR;
|
||||
break;
|
||||
}
|
||||
if (ret == 0) {
|
||||
pd_decode_command(pd, pd->rx_buf, pd->rx_buf_len);
|
||||
}
|
||||
pd->pd_state = OSDP_PD_STATE_SEND_REPLY;
|
||||
/* FALLTHRU */
|
||||
case OSDP_PD_STATE_SEND_REPLY:
|
||||
if (pd_send_reply(pd) == -1) {
|
||||
pd->pd_state = OSDP_PD_STATE_ERR;
|
||||
break;
|
||||
}
|
||||
pd->rx_buf_len = 0;
|
||||
pd->pd_state = OSDP_PD_STATE_IDLE;
|
||||
break;
|
||||
case OSDP_PD_STATE_ERR:
|
||||
/**
|
||||
* PD error state is momentary as it doesn't maintain any state
|
||||
* between commands. We just clean up secure channel status and
|
||||
* go back to idle state.
|
||||
*/
|
||||
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
|
||||
pd->rx_buf_len = 0;
|
||||
if (pd->channel.flush) {
|
||||
pd->channel.flush(pd->channel.data);
|
||||
}
|
||||
pd->pd_state = OSDP_PD_STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void osdp_pd_set_attributes(struct osdp_pd *pd, struct osdp_pd_cap *cap,
|
||||
struct osdp_pd_id *id)
|
||||
{
|
||||
int fc;
|
||||
|
||||
while (cap && ((fc = cap->function_code) > 0)) {
|
||||
if (fc >= OSDP_PD_CAP_SENTINEL) {
|
||||
break;
|
||||
}
|
||||
pd->cap[fc].function_code = cap->function_code;
|
||||
pd->cap[fc].compliance_level = cap->compliance_level;
|
||||
pd->cap[fc].num_items = cap->num_items;
|
||||
cap++;
|
||||
}
|
||||
if (id != NULL) {
|
||||
memcpy(&pd->id, id, sizeof(struct osdp_pd_id));
|
||||
}
|
||||
}
|
||||
|
||||
int osdp_setup(struct osdp *ctx, struct osdp_channel *channel)
|
||||
{
|
||||
struct osdp_pd *pd;
|
||||
|
||||
if (ctx->cp->num_pd != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pd = TO_PD(ctx, 0);
|
||||
pd->seq_number = -1;
|
||||
pd->address = CONFIG_OSDP_PD_ADDRESS;
|
||||
pd->baud_rate = CONFIG_OSDP_UART_BAUD_RATE;
|
||||
|
||||
memcpy(&pd->channel, channel, sizeof(struct osdp_channel));
|
||||
osdp_pd_set_attributes(pd, osdp_pd_cap, &osdp_pd_id);
|
||||
SET_FLAG(pd, PD_FLAG_PD_MODE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int osdp_pd_get_cmd(struct osdp_cmd *cmd)
|
||||
{
|
||||
sys_snode_t *node;
|
||||
struct osdp_cmd *c;
|
||||
struct osdp_pd *pd = TO_PD(osdp_get_ctx(), 0);
|
||||
|
||||
node = sys_slist_peek_head(&pd->cmd.queue);
|
||||
if (node == NULL) {
|
||||
return -1;
|
||||
}
|
||||
sys_slist_remove(&pd->cmd.queue, NULL, node);
|
||||
c = CONTAINER_OF(node, struct osdp_cmd, node);
|
||||
memcpy(cmd, c, sizeof(struct osdp_cmd));
|
||||
osdp_cmd_free(pd, c);
|
||||
return 0;
|
||||
}
|
||||
277
subsys/mgmt/osdp/src/osdp_phy.c
Normal file
277
subsys/mgmt/osdp/src/osdp_phy.c
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Siddharth Chandrasekaran <siddharth@embedjournal.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
|
||||
|
||||
#include <string.h>
|
||||
#include "osdp_common.h"
|
||||
|
||||
#define TAG "PHY: "
|
||||
|
||||
#define OSDP_PKT_MARK 0xFF
|
||||
#define OSDP_PKT_SOM 0x53
|
||||
#define PKT_CONTROL_SQN 0x03
|
||||
#define PKT_CONTROL_CRC 0x04
|
||||
#define PKT_CONTROL_SCB 0x08
|
||||
|
||||
struct osdp_packet_header {
|
||||
uint8_t mark;
|
||||
uint8_t som;
|
||||
uint8_t pd_address;
|
||||
uint8_t len_lsb;
|
||||
uint8_t len_msb;
|
||||
uint8_t control;
|
||||
uint8_t data[];
|
||||
} __packed;
|
||||
|
||||
uint8_t osdp_compute_checksum(uint8_t *msg, int length)
|
||||
{
|
||||
uint8_t checksum = 0;
|
||||
int i, whole_checksum;
|
||||
|
||||
whole_checksum = 0;
|
||||
for (i = 0; i < length; i++) {
|
||||
whole_checksum += msg[i];
|
||||
checksum = ~(0xff & whole_checksum) + 1;
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
static int osdp_phy_get_seq_number(struct osdp_pd *pd, int do_inc)
|
||||
{
|
||||
/* pd->seq_num is set to -1 to reset phy cmd state */
|
||||
if (do_inc) {
|
||||
pd->seq_number += 1;
|
||||
if (pd->seq_number > 3) {
|
||||
pd->seq_number = 1;
|
||||
}
|
||||
}
|
||||
return pd->seq_number & PKT_CONTROL_SQN;
|
||||
}
|
||||
|
||||
int osdp_phy_packet_get_data_offset(struct osdp_pd *pd, const uint8_t *buf)
|
||||
{
|
||||
int sb_len = 0;
|
||||
struct osdp_packet_header *pkt;
|
||||
|
||||
ARG_UNUSED(pd);
|
||||
pkt = (struct osdp_packet_header *)buf;
|
||||
if (pkt->control & PKT_CONTROL_SCB) {
|
||||
sb_len = pkt->data[0];
|
||||
}
|
||||
return sizeof(struct osdp_packet_header) + sb_len;
|
||||
}
|
||||
|
||||
uint8_t *osdp_phy_packet_get_smb(struct osdp_pd *pd, const uint8_t *buf)
|
||||
{
|
||||
struct osdp_packet_header *pkt;
|
||||
|
||||
ARG_UNUSED(pd);
|
||||
pkt = (struct osdp_packet_header *)buf;
|
||||
if (pkt->control & PKT_CONTROL_SCB) {
|
||||
return pkt->data;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int osdp_phy_in_sc_handshake(int is_reply, int id)
|
||||
{
|
||||
if (is_reply) {
|
||||
return (id == REPLY_CCRYPT || id == REPLY_RMAC_I);
|
||||
} else {
|
||||
return (id == CMD_CHLNG || id == CMD_SCRYPT);
|
||||
}
|
||||
}
|
||||
|
||||
int osdp_phy_packet_init(struct osdp_pd *pd, uint8_t *buf, int max_len)
|
||||
{
|
||||
int exp_len, pd_mode, sb_len, id;
|
||||
struct osdp_packet_header *pkt;
|
||||
|
||||
pd_mode = ISSET_FLAG(pd, PD_FLAG_PD_MODE);
|
||||
exp_len = sizeof(struct osdp_packet_header) + 64; /* 64 is estimated */
|
||||
if (max_len < exp_len) {
|
||||
LOG_INF(TAG "packet_init: out of space! CMD: %02x", pd->cmd_id);
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
|
||||
/* Fill packet header */
|
||||
pkt = (struct osdp_packet_header *)buf;
|
||||
pkt->mark = OSDP_PKT_MARK;
|
||||
pkt->som = OSDP_PKT_SOM;
|
||||
pkt->pd_address = pd->address & 0x7F; /* Use only the lower 7 bits */
|
||||
if (pd_mode) {
|
||||
/* PD must reply with MSB of it's address set */
|
||||
pkt->pd_address |= 0x80;
|
||||
id = pd->reply_id;
|
||||
} else {
|
||||
id = pd->cmd_id;
|
||||
}
|
||||
pkt->control = osdp_phy_get_seq_number(pd, !pd_mode);
|
||||
pkt->control |= PKT_CONTROL_CRC;
|
||||
|
||||
sb_len = 0;
|
||||
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
|
||||
pkt->control |= PKT_CONTROL_SCB;
|
||||
pkt->data[0] = sb_len = 2;
|
||||
pkt->data[1] = SCS_15;
|
||||
} else if (osdp_phy_in_sc_handshake(pd_mode, id)) {
|
||||
pkt->control |= PKT_CONTROL_SCB;
|
||||
pkt->data[0] = sb_len = 3;
|
||||
pkt->data[1] = SCS_11;
|
||||
}
|
||||
|
||||
return sizeof(struct osdp_packet_header) + sb_len;
|
||||
}
|
||||
|
||||
int osdp_phy_packet_finalize(struct osdp_pd *pd, uint8_t *buf,
|
||||
int len, int max_len)
|
||||
{
|
||||
uint16_t crc16;
|
||||
struct osdp_packet_header *pkt;
|
||||
|
||||
/* Do a sanity check only as we expect expect header to be prefilled */
|
||||
if (buf[0] != OSDP_PKT_MARK || buf[1] != OSDP_PKT_SOM) {
|
||||
LOG_ERR(TAG "packet_finalize: header validation failed! "
|
||||
"CMD: %02x", pd->cmd_id);
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
pkt = (struct osdp_packet_header *)buf;
|
||||
|
||||
/* len: with 2 byte CRC; without 1 byte mark */
|
||||
pkt->len_lsb = BYTE_0(len - 1 + 2);
|
||||
pkt->len_msb = BYTE_1(len - 1 + 2);
|
||||
|
||||
/* fill crc16 */
|
||||
if (len + 2 > max_len) {
|
||||
goto out_of_space_error;
|
||||
}
|
||||
crc16 = osdp_compute_crc16(buf + 1, len - 1); /* excluding mark byte */
|
||||
buf[len + 0] = BYTE_0(crc16);
|
||||
buf[len + 1] = BYTE_1(crc16);
|
||||
len += 2;
|
||||
|
||||
return len;
|
||||
|
||||
out_of_space_error:
|
||||
LOG_ERR(TAG "packet_finalize: Out of buffer space! "
|
||||
"CMD: %02x", pd->cmd_id);
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
|
||||
int osdp_phy_decode_packet(struct osdp_pd *pd, uint8_t *buf, int len)
|
||||
{
|
||||
uint8_t *data;
|
||||
uint16_t comp, cur;
|
||||
int pkt_len, pd_mode, pd_addr, mac_offset;
|
||||
struct osdp_packet_header *pkt;
|
||||
|
||||
pd_mode = ISSET_FLAG(pd, PD_FLAG_PD_MODE);
|
||||
pkt = (struct osdp_packet_header *)buf;
|
||||
|
||||
/* wait till we have the header */
|
||||
if ((unsigned long)len < sizeof(struct osdp_packet_header)) {
|
||||
/* incomplete data */
|
||||
return OSDP_ERR_PKT_WAIT;
|
||||
}
|
||||
|
||||
/* validate packet header */
|
||||
if (pkt->mark != OSDP_PKT_MARK || pkt->som != OSDP_PKT_SOM) {
|
||||
LOG_ERR(TAG "invalid MARK/SOM");
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
|
||||
if (!pd_mode && !(pkt->pd_address & 0x80)) {
|
||||
LOG_ERR(TAG "reply without MSB set 0x%02x", pkt->pd_address);
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
|
||||
/* validate packet length */
|
||||
pkt_len = (pkt->len_msb << 8) | pkt->len_lsb;
|
||||
if (pkt_len != len - 1) {
|
||||
/* wait for more data? */
|
||||
return OSDP_ERR_PKT_WAIT;
|
||||
}
|
||||
|
||||
/* validate PD address */
|
||||
pd_addr = pkt->pd_address & 0x7F;
|
||||
if (pd_addr != pd->address && pd_addr != 0x7F) {
|
||||
/* not addressed to us and was not broadcasted */
|
||||
if (!pd_mode) {
|
||||
LOG_ERR(TAG "invalid pd address %d", pd_addr);
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
LOG_DBG(TAG "cmd for PD[%d] discarded", pd_addr);
|
||||
return OSDP_ERR_PKT_SKIP;
|
||||
}
|
||||
|
||||
/* validate sequence number */
|
||||
cur = pkt->control & PKT_CONTROL_SQN;
|
||||
if (pd_mode && cur == 0) {
|
||||
/**
|
||||
* CP is trying to restart communication by sending a 0. The
|
||||
* current PD implementation does not hold any state between
|
||||
* commands so we can just set seq_number to -1 (so it gets
|
||||
* incremented to 0 with a call to phy_get_seq_number()) and
|
||||
* invalidate any established secure channels.
|
||||
*/
|
||||
pd->seq_number = -1;
|
||||
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
|
||||
}
|
||||
if (pd_mode && cur == pd->seq_number) {
|
||||
/**
|
||||
* TODO: PD must resend the last response if CP send the same
|
||||
* sequence number again.
|
||||
*/
|
||||
LOG_ERR(TAG "seq-repeat reply-resend feature not supported!");
|
||||
pd->reply_id = REPLY_NAK;
|
||||
pd->cmd_data[0] = OSDP_PD_NAK_SEQ_NUM;
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
comp = osdp_phy_get_seq_number(pd, pd_mode);
|
||||
if (comp != cur && !ISSET_FLAG(pd, PD_FLAG_SKIP_SEQ_CHECK)) {
|
||||
LOG_ERR(TAG "packet seq mismatch %d/%d", comp, cur);
|
||||
pd->reply_id = REPLY_NAK;
|
||||
pd->cmd_data[0] = OSDP_PD_NAK_SEQ_NUM;
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
len -= sizeof(struct osdp_packet_header); /* consume header */
|
||||
|
||||
/* validate CRC/checksum */
|
||||
if (pkt->control & PKT_CONTROL_CRC) {
|
||||
cur = (buf[pkt_len] << 8) | buf[pkt_len - 1];
|
||||
comp = osdp_compute_crc16(buf + 1, pkt_len - 2);
|
||||
if (comp != cur) {
|
||||
LOG_ERR(TAG "invalid crc 0x%04x/0x%04x", comp, cur);
|
||||
pd->reply_id = REPLY_NAK;
|
||||
pd->cmd_data[0] = OSDP_PD_NAK_MSG_CHK;
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
mac_offset = pkt_len - 4 - 2;
|
||||
len -= 2; /* consume CRC */
|
||||
} else {
|
||||
comp = osdp_compute_checksum(buf + 1, pkt_len - 1);
|
||||
if (comp != buf[len - 1]) {
|
||||
LOG_ERR(TAG "invalid checksum %02x/%02x", comp, cur);
|
||||
pd->reply_id = REPLY_NAK;
|
||||
pd->cmd_data[0] = OSDP_PD_NAK_MSG_CHK;
|
||||
return OSDP_ERR_PKT_FMT;
|
||||
}
|
||||
mac_offset = pkt_len - 4 - 1;
|
||||
len -= 1; /* consume checksum */
|
||||
}
|
||||
|
||||
data = pkt->data;
|
||||
memmove(buf, data, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
void osdp_phy_state_reset(struct osdp_pd *pd)
|
||||
{
|
||||
pd->seq_number = -1;
|
||||
pd->rx_buf_len = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user