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