Mistake on this page?
Report an issue in GitHub or email us
OSPIFBlockDevice.h
1 /* mbed Microcontroller Library
2  * Copyright (c) 2020 ARM Limited
3  * SPDX-License-Identifier: Apache-2.0
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 #ifndef MBED_OSPIF_BLOCK_DEVICE_H
18 #define MBED_OSPIF_BLOCK_DEVICE_H
19 
20 #include "drivers/OSPI.h"
21 #include "blockdevice/internal/SFDP.h"
22 #include "blockdevice/BlockDevice.h"
23 #include "platform/Callback.h"
24 
25 #if defined(TARGET_MX25LM51245G)
26 #include "MX25LM51245G_config.h"
27 #endif
28 
29 #if defined(TARGET_MX25LW51245G)
30 #include "MX25LW51245G_config.h"
31 #endif
32 
33 #ifndef MBED_CONF_OSPIF_OSPI_IO0
34 #define MBED_CONF_OSPIF_OSPI_IO0 NC
35 #endif
36 #ifndef MBED_CONF_OSPIF_OSPI_IO1
37 #define MBED_CONF_OSPIF_OSPI_IO1 NC
38 #endif
39 #ifndef MBED_CONF_OSPIF_OSPI_IO2
40 #define MBED_CONF_OSPIF_OSPI_IO2 NC
41 #endif
42 #ifndef MBED_CONF_OSPIF_OSPI_IO3
43 #define MBED_CONF_OSPIF_OSPI_IO3 NC
44 #endif
45 #ifndef MBED_CONF_OSPIF_OSPI_IO4
46 #define MBED_CONF_OSPIF_OSPI_IO4 NC
47 #endif
48 #ifndef MBED_CONF_OSPIF_OSPI_IO5
49 #define MBED_CONF_OSPIF_OSPI_IO5 NC
50 #endif
51 #ifndef MBED_CONF_OSPIF_OSPI_IO6
52 #define MBED_CONF_OSPIF_OSPI_IO6 NC
53 #endif
54 #ifndef MBED_CONF_OSPIF_OSPI_IO7
55 #define MBED_CONF_OSPIF_OSPI_IO7 NC
56 #endif
57 #ifndef MBED_CONF_OSPIF_OSPI_SCK
58 #define MBED_CONF_OSPIF_OSPI_SCK NC
59 #endif
60 #ifndef MBED_CONF_OSPIF_OSPI_CSN
61 #define MBED_CONF_OSPIF_OSPI_CSN NC
62 #endif
63 #ifndef MBED_CONF_OSPIF_OSPI_DQS
64 #define MBED_CONF_OSPIF_OSPI_DQS NC
65 #endif
66 #ifndef MBED_CONF_OSPIF_OSPI_POLARITY_MODE
67 #define MBED_CONF_OSPIF_OSPI_POLARITY_MODE 0
68 #endif
69 #ifndef MBED_CONF_OSPIF_OSPI_FREQ
70 #define MBED_CONF_OSPIF_OSPI_FREQ 40000000
71 #endif
72 
73 /** Enum ospif standard error codes
74  *
75  * @enum ospif_bd_error
76  */
77 enum ospif_bd_error {
78  OSPIF_BD_ERROR_OK = 0, /*!< no error */
79  OSPIF_BD_ERROR_DEVICE_ERROR = mbed::BD_ERROR_DEVICE_ERROR, /*!< device specific error -4001 */
80  OSPIF_BD_ERROR_PARSING_FAILED = -4002, /* SFDP Parsing failed */
81  OSPIF_BD_ERROR_READY_FAILED = -4003, /* Wait for Mem Ready failed */
82  OSPIF_BD_ERROR_WREN_FAILED = -4004, /* Write Enable Failed */
83  OSPIF_BD_ERROR_INVALID_ERASE_PARAMS = -4005, /* Erase command not on sector aligned addresses or exceeds device size */
84  OSPIF_BD_ERROR_DEVICE_NOT_UNIQUE = -4006, /* Only one instance per csel is allowed */
85  OSPIF_BD_ERROR_DEVICE_MAX_EXCEED = -4007 /* Max active OSPIF devices exceeded */
86 };
87 
88 enum _mode {
89  SPI,
90  SOPI,
91  DOPI
92 };
93 
94 /** Enum ospif polarity mode
95  *
96  * @enum ospif_polarity_mode
97  */
98 enum ospif_polarity_mode {
99  OSPIF_POLARITY_MODE_0 = 0, /* CPOL=0, CPHA=0 */
100  OSPIF_POLARITY_MODE_1 /* CPOL=1, CPHA=1 */
101 };
102 
103 #define OSPIF_MAX_ACTIVE_FLASH_DEVICES 10
104 
105 /** BlockDevice for SFDP based flash devices over OSPI bus
106  *
107  * @code
108  * // Here's an example using OSPI flash device on NUCLEO_L4R9I target
109  * #include "mbed.h"
110  * #include "OSPIFBlockDevice.h"
111  *
112  * OSPIFBlockDevice block_device(OSPI_FLASH1_IO0, OSPI_FLASH1_IO1, OSPI_FLASH1_IO2, OSPI_FLASH1_IO3, OSPI_FLASH1_IO4, OSPI_FLASH1_IO5, OSPI_FLASH1_IO6, OSPI_FLASH1_IO7
113  * OSPI_FLASH1_SCK, OSPI_FLASH1_CSN, OSPI_FLASH1_DQS, OSPIF_POLARITY_MODE_0, MBED_CONF_OSPIF_OSPI_FREQ);
114  *
115  * int main()
116  * {
117  * printf("OSPI SFDP Flash Block Device example\n");
118  *
119  * // Initialize the OSPI flash device and print the memory layout
120  * block_device.init();
121  * bd_size_t sector_size_at_address_0 = block_device.get_erase_size(0);
122  *
123  * printf("OSPIF BD size: %llu\n", block_device.size());
124  * printf("OSPIF BD read size: %llu\n", block_device.get_read_size());
125  * printf("OSPIF BD program size: %llu\n", block_device.get_program_size());
126  * printf("OSPIF BD erase size (at address 0): %llu\n", sector_size_at_address_0);
127  *
128  * // Write "Hello World!" to the first block
129  * char *buffer = (char *) malloc(sector_size_at_address_0);
130  * sprintf(buffer, "Hello World!\n");
131  * block_device.erase(0, sector_size_at_address_0);
132  * block_device.program(buffer, 0, sector_size_at_address_0);
133  *
134  * // Read back what was stored
135  * block_device.read(buffer, 0, sector_size_at_address_0);
136  * printf("%s", buffer);
137  *
138  * // Deinitialize the device
139  * block_device.deinit();
140  * }
141  * @endcode
142  */
144 public:
145  /** Create OSPIFBlockDevice - An SFDP based Flash Block Device over OSPI bus
146  *
147  * @param io0 1st IO pin used for sending/receiving data during data phase of a transaction
148  * @param io1 2nd IO pin used for sending/receiving data during data phase of a transaction
149  * @param io2 3rd IO pin used for sending/receiving data during data phase of a transaction
150  * @param io3 4th IO pin used for sending/receiving data during data phase of a transaction
151  * @param io4 5th IO pin used for sending/receiving data during data phase of a transaction
152  * @param io5 6th IO pin used for sending/receiving data during data phase of a transaction
153  * @param io6 7th IO pin used for sending/receiving data during data phase of a transaction
154  * @param io7 8th IO pin used for sending/receiving data during data phase of a transaction
155  * @param sclk OSPI Clock pin
156  * @param csel OSPI chip select pin
157  * @param dqs OSPI dqs pin
158  * @param clock_mode specifies the OSPI Clock Polarity mode (OSPIF_POLARITY_MODE_0/OSPIF_POLARITY_MODE_1)
159  * default value = 0
160  * @param freq Clock frequency of the OSPI bus (defaults to 40MHz)
161  */
162  OSPIFBlockDevice(PinName io0 = MBED_CONF_OSPIF_OSPI_IO0,
163  PinName io1 = MBED_CONF_OSPIF_OSPI_IO1,
164  PinName io2 = MBED_CONF_OSPIF_OSPI_IO2,
165  PinName io3 = MBED_CONF_OSPIF_OSPI_IO3,
166  PinName io4 = MBED_CONF_OSPIF_OSPI_IO4,
167  PinName io5 = MBED_CONF_OSPIF_OSPI_IO5,
168  PinName io6 = MBED_CONF_OSPIF_OSPI_IO6,
169  PinName io7 = MBED_CONF_OSPIF_OSPI_IO7,
170  PinName sclk = MBED_CONF_OSPIF_OSPI_SCK,
171  PinName csel = MBED_CONF_OSPIF_OSPI_CSN,
172  PinName dqs = MBED_CONF_OSPIF_OSPI_DQS,
173  int clock_mode = MBED_CONF_OSPIF_OSPI_POLARITY_MODE,
174  int freq = MBED_CONF_OSPIF_OSPI_FREQ);
175 
176  /** Initialize a block device
177  *
178  * @return OSPIF_BD_ERROR_OK(0) - success
179  * OSPIF_BD_ERROR_DEVICE_ERROR - device driver transaction failed
180  * OSPIF_BD_ERROR_READY_FAILED - Waiting for Memory ready failed or timedout
181  * OSPIF_BD_ERROR_PARSING_FAILED - unexpected format or values in one of the SFDP tables
182  */
183  virtual int init();
184 
185  /** Deinitialize a block device
186  *
187  * @return OSPIF_BD_ERROR_OK(0) - success
188  * OSPIF_BD_ERROR_DEVICE_ERROR - device driver transaction failed
189  */
190  virtual int deinit();
191 
192  /** Desctruct OSPIFBlockDevie
193  */
195  {
196  deinit();
197  }
198 
199  /** Read blocks from a block device
200  *
201  * @param buffer Buffer to write blocks to
202  * @param addr Address of block to begin reading from
203  * @param size Size to read in bytes, must be a multiple of read block size
204  * @return OSPIF_BD_ERROR_OK(0) - success
205  * OSPIF_BD_ERROR_DEVICE_ERROR - device driver transaction failed
206  */
207  virtual int read(void *buffer, mbed::bd_addr_t addr, mbed::bd_size_t size);
208 
209  /** Program blocks to a block device
210  *
211  * The blocks must have been erased prior to being programmed
212  *
213  * @param buffer Buffer of data to write to blocks
214  * @param addr Address of block to begin writing to
215  * @param size Size to write in bytes, must be a multiple of program block size
216  * @return OSPIF_BD_ERROR_OK(0) - success
217  * OSPIF_BD_ERROR_DEVICE_ERROR - device driver transaction failed
218  * OSPIF_BD_ERROR_READY_FAILED - Waiting for Memory ready failed or timed out
219  * OSPIF_BD_ERROR_WREN_FAILED - Write Enable failed
220  * OSPIF_BD_ERROR_PARSING_FAILED - unexpected format or values in one of the SFDP tables
221  */
222  virtual int program(const void *buffer, mbed::bd_addr_t addr, mbed::bd_size_t size);
223 
224  /** Erase blocks on a block device
225  *
226  * The state of an erased block is undefined until it has been programmed
227  *
228  * @param addr Address of block to begin erasing
229  * @param size Size to erase in bytes, must be a multiple of erase block size
230  * @return OSPIF_BD_ERROR_OK(0) - success
231  * OSPIF_BD_ERROR_DEVICE_ERROR - device driver transaction failed
232  * OSPIF_BD_ERROR_READY_FAILED - Waiting for Memory ready failed or timed out
233  * OSPIF_BD_ERROR_WREN_FAILED - Write Enable failed
234  * OSPIF_BD_ERROR_PARSING_FAILED - unexpected format or values in one of the SFDP tables
235  * OSPIF_BD_ERROR_INVALID_ERASE_PARAMS - Trying to erase unaligned address or size
236  */
237  virtual int erase(mbed::bd_addr_t addr, mbed::bd_size_t size);
238 
239  /** Get the size of a readable block
240  *
241  * @return Size of a readable block in bytes
242  */
243  virtual mbed::bd_size_t get_read_size() const;
244 
245  /** Get the size of a programable block
246  *
247  * @return Size of a program block size in bytes
248  * @note Must be a multiple of the read size
249  */
250  virtual mbed::bd_size_t get_program_size() const;
251 
252  /** Get the size of a eraseable block
253  *
254  * @return Size of a minimal erase block, common to all regions, in bytes
255  * @note Must be a multiple of the program size
256  */
257  virtual mbed::bd_size_t get_erase_size() const;
258 
259  /** Get the size of minimal eraseable sector size of given address
260  *
261  * @param addr Any address within block queried for erase sector size (can be any address within flash size offset)
262  * @return Size of minimal erase sector size, in given address region, in bytes
263  * @note Must be a multiple of the program size
264  */
265  virtual mbed::bd_size_t get_erase_size(mbed::bd_addr_t addr);
266 
267  /** Get the value of storage byte after it was erased
268  *
269  * If get_erase_value returns a non-negative byte value, the underlying
270  * storage is set to that value when erased, and storage containing
271  * that value can be programmed without another erase.
272  *
273  * @return The value of storage when erased, or -1 if you can't
274  * rely on the value of erased storage
275  */
276  virtual int get_erase_value() const;
277 
278  /** Get the total size of the underlying device
279  *
280  * @return Size of the underlying device in bytes
281  */
282  virtual mbed::bd_size_t size() const;
283 
284  /** Get the BlockDevice class type.
285  *
286  * @return A string represent the BlockDevice class type.
287  */
288  virtual const char *get_type() const;
289 
290  /** Change the operation mode(SPI, SOPI or DOPI) of flash.
291  *
292  * @return OSPIF_BD_ERROR_OK(0) - success
293  * OSPIF_BD_ERROR_DEVICE_ERROR - device driver transaction failed
294  * OSPIF_BD_ERROR_READY_FAILED - Waiting for Memory ready failed or timed out
295  * OSPIF_BD_ERROR_WREN_FAILED - Write Enable failed
296  * OSPIF_BD_ERROR_PARSING_FAILED - unexpected format or values in one of the SFDP tables
297  * OSPIF_BD_ERROR_INVALID_ERASE_PARAMS - Trying to erase unaligned address or size
298  */
299  virtual int change_mode(int mode);
300 
301 private:
302  /********************************/
303  /* Different Device Csel Mgmt */
304  /********************************/
305  // Add a new OSPI device CS to existing devices list.
306  // Only one OSPIFBlockDevice instance per CS is allowed
307  int add_new_csel_instance(PinName csel);
308 
309  // Remove device CS from existing device list upon destroying object (last deinit is called)
310  int remove_csel_instance(PinName csel);
311 
312  /********************************/
313  /* Calls to OSPI Driver APIs */
314  /********************************/
315  // Send Program/Write command to Driver
316  ospi_status_t _ospi_send_program_command(mbed::ospi_inst_t prog_instruction, const void *buffer,
317  mbed::bd_addr_t addr, mbed::bd_size_t *size);
318 
319  // Send Read command to Driver
320  ospi_status_t _ospi_send_read_command(mbed::ospi_inst_t read_instruction, void *buffer, mbed::bd_addr_t addr, mbed::bd_size_t size);
321 
322  // Send Erase Instruction using command_transfer command to Driver
323  ospi_status_t _ospi_send_erase_command(mbed::ospi_inst_t erase_instruction, mbed::bd_addr_t addr, mbed::bd_size_t size);
324 
325  // Send Generic command_transfer command to Driver
326  ospi_status_t _ospi_send_general_command(mbed::ospi_inst_t instruction_int, mbed::bd_addr_t addr, const char *tx_buffer,
327  mbed::bd_size_t tx_length, const char *rx_buffer, mbed::bd_size_t rx_length);
328 
329  // Send command to read from the SFDP table
330  int _ospi_send_read_sfdp_command(mbed::bd_addr_t addr, void *rx_buffer, mbed::bd_size_t rx_length);
331 
332  // Read the contents of status registers 1 and 2 into a buffer (buffer must have a length of 2)
333  ospi_status_t _ospi_read_status_registers(uint8_t *reg_buffer);
334 
335  // Set the contents of status registers 1 and 2 from a buffer (buffer must have a length of 2)
336  ospi_status_t _ospi_write_status_registers(uint8_t *reg_buffer);
337 
338  // Send set_frequency command to Driver
339  ospi_status_t _ospi_set_frequency(int freq);
340 
341  // Update the 4-byte addressing extension register with the MSB of the address if it is in use
342  ospi_status_t _ospi_update_4byte_ext_addr_reg(mbed::bd_addr_t addr);
343 
344  /*********************************/
345  /* Flash Configuration Functions */
346  /*********************************/
347  // Clear the device's block protection
348  int _clear_block_protection();
349 
350  // Configure Write Enable in Status Register
351  int _set_write_enable();
352 
353  // Wait on status register until write not-in-progress
354  bool _is_mem_ready();
355 
356  // Enable Fast Mode - for flash chips with low power default
357  int _enable_fast_mode();
358 
359  // Enable OPI Mode
360  int _enable_opi_mdoe();
361 
362  // Query vendor ID and handle special behavior that isn't covered by SFDP data
363  int _handle_vendor_quirks();
364 
365  /****************************************/
366  /* SFDP Detection and Parsing Functions */
367  /****************************************/
368  // Parse and Detect required Basic Parameters from Table
369  int _sfdp_parse_basic_param_table(mbed::Callback<int(mbed::bd_addr_t, void *, mbed::bd_size_t)> sfdp_reader,
370  mbed::sfdp_hdr_info &sfdp_info);
371 
372  // Parse and Detect 4-Byte Address Instruction Parameters from Table
373  int _sfdp_parse_4_byte_inst_table(mbed::Callback<int(mbed::bd_addr_t, void *, mbed::bd_size_t)> sfdp_reader,
374  mbed::sfdp_hdr_info &sfdp_info);
375 
376  // Detect the soft reset protocol and reset - returns error if soft reset is not supported
377  int _sfdp_detect_reset_protocol_and_reset(uint8_t *basic_param_table_ptr);
378 
379  // Detect fastest read Bus mode supported by device
380  int _sfdp_detect_best_bus_read_mode(uint8_t *basic_param_table_ptr, int basic_param_table_size,
381  bool &set_quad_enable, bool &is_qpi_mode, bool &is_opi_mode);
382 
383  // Enable Quad mode if supported (1-1-4, 1-4-4, 4-4-4 bus modes)
384  int _sfdp_set_quad_enabled(uint8_t *basic_param_table_ptr);
385 
386  // Enable QPI mode (4-4-4)
387  int _sfdp_set_qpi_enabled(uint8_t *basic_param_table_ptr);
388 
389  // Detect 4-byte addressing mode and enable it if supported
390  int _sfdp_detect_and_enable_4byte_addressing(uint8_t *basic_param_table_ptr, int basic_param_table_size);
391 
392 #ifdef MX_FLASH_SUPPORT_RWW
393  bool _is_mem_ready_rww(bd_addr_t addr, uint8_t rw);
394 #endif
395 
396 private:
397  enum ospif_clear_protection_method_t {
398  OSPIF_BP_ULBPR, // Issue global protection unlock instruction
399  OSPIF_BP_CLEAR_SR, // Clear protection bits in status register 1
400  };
401 
402  // OSPI Driver Object
403  mbed::OSPI _ospi;
404 
405  // Static List of different OSPI based Flash devices csel that already exist
406  // Each OSPI Flash device csel can have only 1 OSPIFBlockDevice instance
407  // _devices_mutex is used to lock csel list - only one OSPIFBlockDevice instance per csel is allowed
408  static SingletonPtr<PlatformMutex> _devices_mutex;
409  static int _number_of_active_ospif_flash_csel;
410  static PinName *_active_ospif_flash_csel_arr;
411 
412  int _unique_device_status;
413  PinName _csel;
414 
415  // Mutex is used to protect Flash device for some OSPI Driver commands that must be done sequentially with no other commands in between
416  // e.g. (1)Set Write Enable, (2)Program, (3)Wait Memory Ready
417  PlatformMutex _mutex;
418 
419  // Command Instructions
420  mbed::ospi_inst_t _read_instruction;
421  mbed::ospi_inst_t _prog_instruction;
422  mbed::ospi_inst_t _legacy_erase_instruction;
423 
424  // Status register write/read instructions
425  unsigned int _num_status_registers;
426  mbed::ospi_inst_t _write_status_reg_2_inst;
427  mbed::ospi_inst_t _read_status_reg_2_inst; // If three registers, this instruction reads the latter two
428 
429  // Attempt to enable 4-byte addressing. True by default, but may be disabled for some vendors
430  bool _attempt_4_byte_addressing;
431  // 4-byte addressing extension register write instruction
432  mbed::ospi_inst_t _4byte_msb_reg_write_inst;
433 
434  // Flash support 4-Byte instructions,like READ4B(0x13).
435  bool _support_4_byte_inst;
436 
437  // Quad mode enable status register and bit
438  int _quad_enable_register_idx;
439  int _quad_enable_bit;
440 
441  bool _needs_fast_mode;
442 
443  // Clear block protection
444  ospif_clear_protection_method_t _clear_protection_method;
445 
446  // Data extracted from the devices SFDP structure
447  mbed::sfdp_hdr_info _sfdp_info;
448 
449  unsigned int _page_size_bytes; // Page size - 256 Bytes default
450  int _freq;
451 
452  // Bus speed configuration
453  ospi_bus_width_t _inst_width; //Bus width for Instruction phase
454  ospi_inst_size_t _inst_size; //Instruction Size
455  ospi_bus_width_t _address_width; //Bus width for Address phase
456  ospi_address_size_t _address_size; //Number of bits for address
457  ospi_alt_size_t _alt_size; //Number of bits for alt
458  bool _alt_enabled; //Whether alt is enabled
459  uint8_t _dummy_cycles; //Number of Dummy cycles required by Current Bus Mode
460  ospi_bus_width_t _data_width; //Bus width for Data phase
461 
462  uint32_t _init_ref_count;
463  bool _is_initialized;
464 #ifdef MX_FLASH_SUPPORT_RWW
465  enum wait_flag {
466  NOT_STARTED, // no wait is started
467  WRITE_WAIT_STARTED, // write wait is started
468  ERASE_WAIT_STARTED, // erase wait is started
469  };
470  uint32_t _busy_bank; // Current busy bank
471  wait_flag _wait_flag; // wait flag
472  PlatformMutex _busy_mutex;
473 #endif
474 };
475 
476 #endif
virtual int get_erase_value() const
Get the value of storage byte after it was erased.
virtual int deinit()
Deinitialize a block device.
BlockDevice for SFDP based flash devices over OSPI bus.
virtual int change_mode(int mode)
Change the operation mode(SPI, SOPI or DOPI) of flash.
A hardware device capable of writing and reading blocks.
Definition: BlockDevice.h:48
A OSPI Driver, used for communicating with OSPI slave devices.
Definition: OSPI.h:87
virtual const char * get_type() const
Get the BlockDevice class type.
virtual mbed::bd_size_t get_read_size() const
Get the size of a readable block.
virtual int erase(mbed::bd_addr_t addr, mbed::bd_size_t size)
Erase blocks on a block device.
The PlatformMutex class is used to synchronize the execution of threads.
Definition: PlatformMutex.h:47
virtual int init()
Initialize a block device.
SFDP JEDEC Parameter Table info.
Definition: SFDP.h:79
virtual mbed::bd_size_t get_erase_size() const
Get the size of a eraseable block.
virtual mbed::bd_size_t size() const
Get the total size of the underlying device.
virtual mbed::bd_size_t get_program_size() const
Get the size of a programable block.
virtual int program(const void *buffer, mbed::bd_addr_t addr, mbed::bd_size_t size)
Program blocks to a block device.
int ospi_inst_t
Type representing a OSPI instruction.
Definition: OSPI.h:46
virtual int read(void *buffer, mbed::bd_addr_t addr, mbed::bd_size_t size)
Read blocks from a block device.
OSPIFBlockDevice(PinName io0=NC, PinName io1=NC, PinName io2=NC, PinName io3=NC, PinName io4=NC, PinName io5=NC, PinName io6=NC, PinName io7=NC, PinName sclk=NC, PinName csel=NC, PinName dqs=NC, int clock_mode=0, int freq=40000000)
Create OSPIFBlockDevice - An SFDP based Flash Block Device over OSPI bus.
~OSPIFBlockDevice()
Desctruct OSPIFBlockDevie.
Callback class based on template specialization.
Definition: Callback.h:53
Important Information for this Arm website

This site uses cookies to store information on your computer. By continuing to use our site, you consent to our cookies. If you are not happy with the use of these cookies, please review our Cookie Policy to learn how they can be disabled. By disabling cookies, some features of the site will not work.