ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
profinet.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file profinet.h
6 * @brief PROFINET DCP (Discovery and Configuration Protocol) frame codec (PROTOCORE_ENABLE_PROFINET).
7 *
8 * DCP is how PROFINET IO-Devices are discovered and named on the wire before an IO connection exists.
9 * It rides raw L2 (ethertype 0x8892, PROFINET RT; see services/fieldbus/rawl2) with a fixed 10-octet frame header
10 * followed by DCP blocks:
11 *
12 * Header: frameID(2) serviceID(1) serviceType(1) xid(4) responseDelayFactor(2) dataLength(2)
13 * Block: option(1) suboption(1) blockLength(2) [blockInfo(2) for Set/Get responses] value...
14 *
15 * FrameIDs: 0xFEFE Identify-request (multicast), 0xFEFF Identify-response, 0xFEFD Get/Set. This builds
16 * the DCP header + blocks and parses them (walking each block via a callback), so a device answers
17 * Identify (with its NameOfStation / IP / device id) and handles Set (assign name/IP). Pure, zero heap,
18 * host-testable; the raw-L2 transmit is the device step.
19 */
20
21#ifndef PROTOCORE_PROFINET_H
22#define PROTOCORE_PROFINET_H
23
24#include "protocore_config.h" // the entry point: protocore_types.h for the widths
25
26#if PROTOCORE_ENABLE_PROFINET
27
29
30// This module holds nothing between calls, so it carves no borrow and states none. An entry
31// takes one all the same, and never reads it, so every namespace in the tree is invoked the
32// same way.
33
34#define PN_FRAMEID_DCP_HELLO 0xFEFC
35#define PN_FRAMEID_DCP_GETSET 0xFEFD
36#define PN_FRAMEID_DCP_IDENT_REQ 0xFEFE
37#define PN_FRAMEID_DCP_IDENT_RES 0xFEFF
38#define PN_DCP_SERVICE_GET 0x03
39#define PN_DCP_SERVICE_SET 0x04
40#define PN_DCP_SERVICE_IDENTIFY 0x05
41#define PN_DCP_TYPE_REQUEST 0x00
42#define PN_DCP_TYPE_RESPONSE_SUCCESS 0x01
43#define PN_DCP_OPT_IP 0x01
44#define PN_DCP_SUB_IP_PARAM 0x02 ///< IP address / subnet / gateway.
45#define PN_DCP_OPT_DEVICE 0x02
46#define PN_DCP_SUB_DEV_NAME_OF_STATION 0x02
47#define PN_DCP_SUB_DEV_ID 0x03
48#define PN_DCP_OPT_ALL 0xFF
49#define PN_DCP_SUB_ALL 0xFF
50#define PN_DCP_HDR_LEN 12 ///< frameID(2) + the 10-octet DCP header
51
52/** @brief A parsed DCP frame header. */
53typedef struct
54{
55 uint16_t frame_id;
56 uint8_t service_id;
57 uint8_t service_type;
58 uint32_t xid;
59 uint16_t response_delay;
60 uint16_t data_length;
61} PnDcpHeader;
62
63/** @brief One DCP block surfaced by protocore_pn_dcp_walk. */
64typedef void (*protocore_pn_dcp_block_cb)(uint8_t option, uint8_t suboption, const uint8_t *value, size_t value_len,
65 void *arg);
66
67/** @brief What dcp_header takes: frame_id, service_id, service_type, ... */
68typedef struct
69{
70 uint16_t frame_id;
71 uint8_t service_id;
72 uint8_t service_type;
73 uint32_t xid;
74 uint16_t response_delay; ///< ResponseDelayFactor: the window a device randomizes its Identify response over, in
75 ///< units of ...
76 uint16_t data_length; ///< the total length of the DCP blocks that follow (filled into the header)
77 uint8_t *out;
78 size_t cap;
79} ProfinetDcpHeaderArgs;
80
81/** @brief What dcp_block takes: option, suboption, value, value_len, ... */
82typedef struct
83{
84 uint8_t option;
85 uint8_t suboption;
86 const uint8_t *value;
87 size_t value_len;
88 uint8_t *out;
89 size_t cap;
90} ProfinetDcpBlockArgs;
91
92/** @brief What dcp_parse_header takes: frame, len, out. */
93typedef struct
94{
95 const uint8_t *frame;
96 size_t len;
97 PnDcpHeader *out;
98} ProfinetDcpParseHeaderArgs;
99
100/** @brief What dcp_walk takes: blocks, len, cb, arg. */
101typedef struct
102{
103 const uint8_t *blocks;
104 size_t len;
105 protocore_pn_dcp_block_cb cb;
106 void *arg;
107} ProfinetDcpWalkArgs;
108
109/**
110 * @brief PROFINET DCP (Discovery and Configuration Protocol) frame codec (PROTOCORE_ENABLE_PROFINET).
111 *
112 * A caller sets the members a call takes, invokes it through ::Profinet with the bytes it runs
113 * out of, and reads the outcome off the same handle.
114 *
115 * Profinet.dcp_header_args.frame_id = ...;
116 * Profinet.dcp_header_args.service_id = ...;
117 * Profinet.dcp_header_args.service_type = ...;
118 * Profinet.dcp_header_args.xid = ...;
119 * Profinet.dcp_header_args.response_delay = ...;
120 * Profinet.dcp_header_args.data_length = ...;
121 * Profinet.dcp_header_args.out = ...;
122 * Profinet.dcp_header_args.cap = ...;
123 * Profinet.dcp_header(work);
124 * // Profinet.n is what the call reports
125 *
126 * @var ProfinetNs::dcp_header_args what dcp_header takes: frame_id, service_id, service_type,
127 * @var ProfinetNs::dcp_block_args what dcp_block takes: option, suboption, value, value_len,
128 * @var ProfinetNs::dcp_parse_header_args what dcp_parse_header takes: frame, len, out
129 * @var ProfinetNs::dcp_walk_args what dcp_walk takes: blocks, len, cb, arg
130 * @var ProfinetNs::ok true if every block fits; invokes cb per block (value excludes the ...
131 * @var ProfinetNs::n the block length written (4 + value_len, padded to even per DCP), ...
132 * @var ProfinetNs::dcp_header build a DCP frame header into out (>= PN_DCP_HDR_LEN bytes). 12, or ...
133 * @var ProfinetNs::dcp_block append a DCP block `[option][suboption][blockLength][value...]` (no ...
134 * @var ProfinetNs::dcp_parse_header parse the DCP header. true if len >= PN_DCP_HDR_LEN
135 * @var ProfinetNs::dcp_walk walk the DCP blocks after the header (blocks points at header+10, ...
136 *
137 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
138 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
139 * a caller drives every namespace the same way.
140 */
141typedef struct
142{
143 ProfinetDcpHeaderArgs dcp_header_args;
144 ProfinetDcpBlockArgs dcp_block_args;
145 ProfinetDcpParseHeaderArgs dcp_parse_header_args;
146 ProfinetDcpWalkArgs dcp_walk_args;
147 proto_bool ok;
148 size_t n;
149} ProfinetVars;
150
151/** @brief The operands and the outcome. */
152extern ProfinetVars ProfinetV;
153
154/** @brief The entries. */
155typedef struct
156{
157 void (*const dcp_header)(uint8_t *work);
158 void (*const dcp_block)(uint8_t *work);
159 void (*const dcp_parse_header)(uint8_t *work);
160 void (*const dcp_walk)(uint8_t *work);
161} ProfinetNs;
162
163// What the table binds, defined once in the .c and taking one parameter each: everything
164// else an entry needs is an operand in ProfinetV or a region of the borrow at a fixed offset.
165void protocore_profinet_dcp_header(uint8_t *work);
166void protocore_profinet_dcp_block(uint8_t *work);
167void protocore_profinet_dcp_parse_header(uint8_t *work);
168void protocore_profinet_dcp_walk(uint8_t *work);
169
170// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
171// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
172// `Profinet.dcp_header(work)` resolves to a named function and becomes a DIRECT call. An extern table
173// leaves the call indirect and the symbol live at every level, -O2 -flto included.
174static const ProfinetNs Profinet __attribute__((unused)) = {
175 .dcp_header = protocore_profinet_dcp_header,
176 .dcp_block = protocore_profinet_dcp_block,
177 .dcp_parse_header = protocore_profinet_dcp_parse_header,
178 .dcp_walk = protocore_profinet_dcp_walk,
179};
180
182
183#endif // PROTOCORE_ENABLE_PROFINET
184
185#endif // PROTOCORE_PROFINET_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97