1 /*
2 * Copyright (c) 2022, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must strain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "configuration.hpp"
30
31 #include "platform-posix.h"
32 #include <openthread/platform/radio.h>
33 #include "lib/platform/exit_code.h"
34 #include "utils/parse_cmdline.hpp"
35
36 #if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE && !OPENTHREAD_POSIX_CONFIG_CONFIGURATION_FILE_ENABLE
37 #error \
38 "OPENTHREAD_POSIX_CONFIG_CONFIGURATION_FILE_ENABLE is required for OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE"
39 #endif
40
41 #if OPENTHREAD_POSIX_CONFIG_CONFIGURATION_FILE_ENABLE
42
43 namespace ot {
44 namespace Posix {
45
46 const char Configuration::kLogModuleName[] = "Config";
47
48 #if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
49 const char Configuration::kKeyCalibratedPower[] = "calibrated_power";
50 #endif
51 const char Configuration::kKeyTargetPower[] = "target_power";
52 const char Configuration::kKeyRegionDomainMapping[] = "region_domain_mapping";
53 const char Configuration::kKeySupportedChannelMask[] = "supported_channel_mask";
54 const char Configuration::kKeyPreferredChannelMask[] = "preferred_channel_mask";
55 const char Configuration::kCommaDelimiter[] = ",";
56
SetRegion(uint16_t aRegionCode)57 otError Configuration::SetRegion(uint16_t aRegionCode)
58 {
59 otError error = OT_ERROR_NONE;
60 Power::Domain domain;
61
62 if (GetDomain(aRegionCode, domain) != OT_ERROR_NONE)
63 {
64 // If failed to find the domain for the region, use the world wide region as the default region.
65 VerifyOrExit(GetDomain(kRegionCodeWorldWide, domain) == OT_ERROR_NONE, error = OT_ERROR_FAILED);
66 }
67
68 SuccessOrExit(error = UpdateChannelMasks(domain));
69 #if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
70 SuccessOrExit(error = UpdateTargetPower(domain));
71 SuccessOrExit(error = UpdateCalibratedPower());
72 #endif
73
74 mRegionCode = aRegionCode;
75
76 exit:
77 if (error == OT_ERROR_NONE)
78 {
79 LogInfo("Successfully set region \"%c%c\"", (aRegionCode >> 8) & 0xff, (aRegionCode & 0xff));
80 }
81 else
82 {
83 LogCrit("Failed to set region \"%c%c\": %s", (aRegionCode >> 8) & 0xff, (aRegionCode & 0xff),
84 otThreadErrorToString(error));
85 }
86
87 return error;
88 }
89
GetDomain(uint16_t aRegionCode,Power::Domain & aDomain)90 otError Configuration::GetDomain(uint16_t aRegionCode, Power::Domain &aDomain)
91 {
92 otError error = OT_ERROR_NOT_FOUND;
93 int iterator = 0;
94 char value[kMaxValueSize];
95 char *str;
96 char *psave;
97
98 while (mProductConfigFile.Get(kKeyRegionDomainMapping, iterator, value, sizeof(value)) == OT_ERROR_NONE)
99 {
100 if ((str = strtok_r(value, kCommaDelimiter, &psave)) == nullptr)
101 {
102 continue;
103 }
104
105 while ((str = strtok_r(nullptr, kCommaDelimiter, &psave)) != nullptr)
106 {
107 if ((strlen(str) == 2) && (StringToRegionCode(str) == aRegionCode))
108 {
109 ExitNow(error = aDomain.Set(value));
110 }
111 }
112 }
113
114 exit:
115 if (error != OT_ERROR_NONE)
116 {
117 LogCrit("Failed to get power domain: %s", otThreadErrorToString(error));
118 }
119
120 return error;
121 }
122
GetChannelMask(const char * aKey,const Power::Domain & aDomain,uint32_t & aChannelMask)123 otError Configuration::GetChannelMask(const char *aKey, const Power::Domain &aDomain, uint32_t &aChannelMask)
124 {
125 otError error = OT_ERROR_NOT_FOUND;
126 int iterator = 0;
127 char value[kMaxValueSize];
128 char *str;
129 Power::Domain domain;
130 uint32_t channelMask;
131 char *psave;
132
133 while (mProductConfigFile.Get(aKey, iterator, value, sizeof(value)) == OT_ERROR_NONE)
134 {
135 if (((str = strtok_r(value, kCommaDelimiter, &psave)) == nullptr) || (aDomain != str))
136 {
137 continue;
138 }
139
140 if ((str = strtok_r(nullptr, kCommaDelimiter, &psave)) != nullptr)
141 {
142 SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(str, channelMask));
143 aChannelMask = channelMask;
144 error = OT_ERROR_NONE;
145 break;
146 }
147 }
148
149 exit:
150 return error;
151 }
152
UpdateChannelMasks(const Power::Domain & aDomain)153 otError Configuration::UpdateChannelMasks(const Power::Domain &aDomain)
154 {
155 otError error = OT_ERROR_NONE;
156
157 if (mProductConfigFile.HasKey(kKeySupportedChannelMask))
158 {
159 SuccessOrExit(error = GetChannelMask(kKeySupportedChannelMask, aDomain, mSupportedChannelMask));
160 }
161
162 if (mProductConfigFile.HasKey(kKeySupportedChannelMask))
163 {
164 SuccessOrExit(error = GetChannelMask(kKeyPreferredChannelMask, aDomain, mPreferredChannelMask));
165 }
166
167 exit:
168 if (error != OT_ERROR_NONE)
169 {
170 LogCrit("Failed to update channel mask: %s", otThreadErrorToString(error));
171 }
172
173 return error;
174 }
175
176 #if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
UpdateTargetPower(const Power::Domain & aDomain)177 otError Configuration::UpdateTargetPower(const Power::Domain &aDomain)
178 {
179 otError error = OT_ERROR_NONE;
180 int iterator = 0;
181 Power::TargetPower targetPower;
182
183 VerifyOrExit(mProductConfigFile.HasKey(kKeyTargetPower));
184
185 while (GetNextTargetPower(aDomain, iterator, targetPower) == OT_ERROR_NONE)
186 {
187 LogInfo("Update target power: %s\r\n", targetPower.ToString().AsCString());
188
189 for (uint8_t ch = targetPower.GetChannelStart(); ch <= targetPower.GetChannelEnd(); ch++)
190 {
191 SuccessOrExit(error = otPlatRadioSetChannelTargetPower(gInstance, ch, targetPower.GetTargetPower()));
192 }
193 }
194
195 exit:
196 if (error != OT_ERROR_NONE)
197 {
198 LogCrit("Failed to update target power: %s", otThreadErrorToString(error));
199 }
200
201 return error;
202 }
203
UpdateCalibratedPower(void)204 otError Configuration::UpdateCalibratedPower(void)
205 {
206 otError error = OT_ERROR_NONE;
207 int iterator = 0;
208 char value[kMaxValueSize];
209 Power::CalibratedPower calibratedPower;
210 ConfigFile *calibrationFile = &mFactoryConfigFile;
211
212 // If the distribution of output power is large, the factory needs to measure the power calibration data
213 // for each device individually, and the power calibration data will be written to the factory config file.
214 // Otherwise, the power calibration data can be pre-configured in the product config file.
215 if (calibrationFile->Get(kKeyCalibratedPower, iterator, value, sizeof(value)) != OT_ERROR_NONE)
216 {
217 calibrationFile = &mProductConfigFile;
218 }
219
220 VerifyOrExit(calibrationFile->HasKey(kKeyCalibratedPower));
221 SuccessOrExit(error = otPlatRadioClearCalibratedPowers(gInstance));
222
223 iterator = 0;
224 while (calibrationFile->Get(kKeyCalibratedPower, iterator, value, sizeof(value)) == OT_ERROR_NONE)
225 {
226 SuccessOrExit(error = calibratedPower.FromString(value));
227 LogInfo("Update calibrated power: %s\r\n", calibratedPower.ToString().AsCString());
228
229 for (uint8_t ch = calibratedPower.GetChannelStart(); ch <= calibratedPower.GetChannelEnd(); ch++)
230 {
231 SuccessOrExit(error = otPlatRadioAddCalibratedPower(gInstance, ch, calibratedPower.GetActualPower(),
232 calibratedPower.GetRawPowerSetting().GetData(),
233 calibratedPower.GetRawPowerSetting().GetLength()));
234 }
235 }
236
237 exit:
238 if (error != OT_ERROR_NONE)
239 {
240 LogCrit("Failed to update calibrated power table: %s", otThreadErrorToString(error));
241 }
242
243 return error;
244 }
245
GetNextTargetPower(const Power::Domain & aDomain,int & aIterator,Power::TargetPower & aTargetPower)246 otError Configuration::GetNextTargetPower(const Power::Domain &aDomain,
247 int &aIterator,
248 Power::TargetPower &aTargetPower)
249 {
250 otError error = OT_ERROR_NOT_FOUND;
251 char value[kMaxValueSize];
252 char *domain;
253 char *psave;
254
255 while (mProductConfigFile.Get(kKeyTargetPower, aIterator, value, sizeof(value)) == OT_ERROR_NONE)
256 {
257 if (((domain = strtok_r(value, kCommaDelimiter, &psave)) == nullptr) || (aDomain != domain))
258 {
259 continue;
260 }
261
262 if ((error = aTargetPower.FromString(psave)) != OT_ERROR_NONE)
263 {
264 LogCrit("Failed to read target power: %s", otThreadErrorToString(error));
265 }
266 break;
267 }
268
269 return error;
270 }
271 #endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
272
IsValid(void) const273 bool Configuration::IsValid(void) const
274 {
275 bool ret;
276
277 VerifyOrExit(mProductConfigFile.DoesExist(), ret = false);
278
279 ret = mProductConfigFile.HasKey(kKeySupportedChannelMask) || mProductConfigFile.HasKey(kKeyPreferredChannelMask) ||
280 mProductConfigFile.HasKey(kKeyRegionDomainMapping);
281 VerifyOrExit(!ret);
282
283 #if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
284 ret = (mProductConfigFile.HasKey(kKeyCalibratedPower) || mProductConfigFile.HasKey(kKeyTargetPower));
285 #endif
286
287 exit:
288 return ret;
289 }
290
291 } // namespace Posix
292 } // namespace ot
293 #endif // OPENTHREAD_POSIX_CONFIG_CONFIGURATION_FILE_ENABLE
294