driver-platform-i3c-develop.md 21.3 KB
Newer Older
A
annie_wangli 已提交
1 2 3 4 5 6
# I3C 

## Overview<a name="1"></a>

The Improved Inter-Integrated Circuit (I3C) is a simple and cost-efficient bidirectional 2-wire synchronous serial bus protocol developed by the Mobile Industry Processor Interface (MIPI) Alliance. In the Hardware Driver Foundation (HDF), the I3C module uses the unified service mode for API adaptation. In this mode, a device service is used as the I3C manager to handle external access requests in a unified manner, which is reflected in the configuration file. The unified service mode applies to the scenario where there are many device objects of the same type, for example, when the I3C has more than 10 controllers. If the independent service mode is used, more device nodes need to be configured and memory resources will be consumed by services.

A
annie_wangli 已提交
7
**Figure 1** Unified service mode<a name="fig1"></a>
A
annie_wangli 已提交
8

A
annie_wangli 已提交
9
![image1](figures/unified-service-mode.png)
A
annie_wangli 已提交
10 11


A
annie_wangli 已提交
12
## Available APIs<a name="2"></a>
A
annie_wangli 已提交
13

A
annie_wangli 已提交
14
**I3cMethod**:
A
annie_wangli 已提交
15 16 17 18 19 20 21 22 23 24 25 26
    ```c
    struct I3cMethod {
        int32_t (*sendCccCmd)(struct I3cCntlr *cntlr, struct I3cCccCmd *ccc);
        int32_t (*transfer)(struct I3cCntlr *cntlr, struct I3cMsg *msgs, int16_t count);
        int32_t (*i2cTransfer)(struct I3cCntlr *cntlr, struct I3cMsg *msgs, int16_t count);
        int32_t (*setConfig)(struct I3cCntlr *cntlr, struct I3cConfig *config);
        int32_t (*getConfig)(struct I3cCntlr *cntlr, struct I3cConfig *config);
        int32_t (*requestIbi)(struct I3cDevice *dev);
        void (*freeIbi)(struct I3cDevice *dev);
    };
    ```

A
annie_wangli 已提交
27 28 29
    **Table 1** APIs for the members in the I3cMethod structure
    
    |Method|Input Parameter|Output Parameter|Return Value|Description|
A
annie_wangli 已提交
30
    |-|-|-|-|-|
A
annie_wangli 已提交
31 32 33 34 35
    |sendCccCmd|**cntlr**: structure pointer to an I3C controller at the core layer. <br/>**ccc**: pointer to the input common command code (CCC) structure.|**ccc**: pointer to the output CCC structure.|HDF_STATUS|Sends a CCC.|
    |Transfer  |**cntlr**: structure pointer to an I3C controller at the core layer. <br/>**msgs**: structure pointer to user messages. <br/>**count**: number of messages, which is of the int16_t type.|**msgs**: structure pointer to user messages.|HDF_STATUS|Transfers user messages in I3C mode.|
    |i2cTransfer |**cntlr**: structure pointer to an I3C controller at the core layer. <br/>**msgs**: structure pointer to user messages. <br>**count**: number of messages, which is of the int16_t type.|**msgs**: structure pointer to user messages.|HDF_STATUS|Transfers user messages in I2C mode.|
    |setConfig|**cntlr**: structure pointer to an I3C controller at the core layer. <br/>**config**: pointer to controller configuration parameters.|–|HDF_STATUS|Sets an I3C controller.|
    |getConfig|**cntlr**: structure pointer to an I3C controller at the core layer.|**config**: pointer to controller configuration parameters.|HDF_STATUS|Obtains the configuration of an I3C controller.|
A
annie_wangli 已提交
36 37 38
    |requestIbi|**device**: structure pointer to an I3C device at the core layer.|–|HDF_STATUS|Requests an IBI for an I3C device.|
    |freeIbi|**device**: structure pointer to an I3C device at the core layer.|–|HDF_STATUS|Releases the IBI for an I3C device.|

A
annie_wangli 已提交
39
## How to Develop<a name="3"></a>
A
annie_wangli 已提交
40

A
annie_wangli 已提交
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
The I3C module adaptation involves the following steps:

1. Instantiate the driver entry.
    - Instantiate the **HdfDriverEntry** structure.
    - Call **HDF_INIT** to register the **HdfDriverEntry** instance with the HDF.

2. Configure attribute files.
   
    - Add the **deviceNode** information to the **device_info.hcs** file.
    - (Optional) Add the **i3c_config.hcs** file.
  
3. Instantiate the I3C controller object.
   
    - Initialize **I3cCntlr**.
    - Instantiate **I3cMethod** in **I3cCntlr**. For details, see [Available APIs](#Available_apis).
  
4. Register an interrupt handler.
    Register an interrupt handler for the controller to implement the device hot-join and in-band interrupt (IBI) features.

  

## Development Example<a name="4"></a>

1. Instantiate the driver entry. The driver entry must be a global variable of the **HdfDriverEntry** type (defined in **hdf\_device\_desc.h**), and the value of **moduleName** must be the same as that in **device\_info.hcs**. In the HDF, the start address of each **HdfDriverEntry** object of all loaded drivers are collected to form a segment address space similar to an array for the upper layer to invoke.
A
annie_wangli 已提交
65 66 67

    Generally, the HDF calls the **Bind** function and then the **Init** function to load a driver. If **Init** fails to be called, the HDF calls **Release** to release driver resources and exit.

A
annie_wangli 已提交
68
    I3C driver entry reference:
A
annie_wangli 已提交
69

A
annie_wangli 已提交
70
    > The I3C module may be connected with multiple controllers. Therefore, in the HDF, a manager object is created for the I3C, and a manager service is published to handle external access requests in a unified manner. Before a controller is opened, the manager service needs to be obtained first. Then, the manager service locates the target controller based on the specified parameters.
A
annie_wangli 已提交
71
    >
A
annie_wangli 已提交
72
    > The core layer implements the driver of the I3C manager service. Vendors do not need to care about the implementation. However, during the implementation of **Init()**, the **I3cCntlrAdd()** function at the core layer needs to be called to implement related features.
A
annie_wangli 已提交
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

    ```c
    static struct HdfDriverEntry g_virtualI3cDriverEntry = {
        .moduleVersion = 1,
        .Init = VirtualI3cInit,
        .Release = VirtualI3cRelease,
        .moduleName = "virtual_i3c_driver",// (Mandatory) The value must be the same as that in the .hcs file.
    };
    HDF_INIT(g_virtualI3cDriverEntry);  // Call HDF_INIT to register the driver entry with the HDF.
    
    /* Driver entry of the i3c_core.c manager service at the core layer */
    struct HdfDriverEntry g_i3cManagerEntry = {
        .moduleVersion = 1,
        .Init     = I3cManagerInit,
        .Release  = I3cManagerRelease,
        .moduleName = "HDF_PLATFORM_I3C_MANAGER",// Correspond to device0 in the device_info file.
    };
    HDF_INIT(g_i3cManagerEntry);
    ```

A
annie_wangli 已提交
93
2. Add **deviceNode** to the **device\_info.hcs** file, and configure the device attributes in the **i3c\_config.hcs** file. The **deviceNode** information is related to registration of the driver entry. The device attribute values are closely related to the driver implementation and the default values or restriction ranges of the **I3cCntlr** members at the core layer.
A
annie_wangli 已提交
94

A
annie_wangli 已提交
95
    In the unified service mode, the first device node in the **device\_info** file must be the I3C manager. The I3C manager parameters must be set as follows:
A
annie_wangli 已提交
96 97 98 99
    
    |Member|Value|
    |-|-|
    |moduleName |HDF_PLATFORM_I3C_MANAGER|
A
annie_wangli 已提交
100
    |serviceName|Reserved|
A
annie_wangli 已提交
101
    |policy|0|
A
annie_wangli 已提交
102
    |cntlrMatchAttr| Reserved |
A
annie_wangli 已提交
103
    
A
annie_wangli 已提交
104
    Configure I3C controller information from the second node. This node specifies a type of I3C controllers rather than a specific I3C controller. In this example, there is only one I3C controller. If there are multiple I3C controllers, you need to add the **deviceNode** information to the **device\_info** file and add the corresponding device attributes to the **i3c\_config** file.
A
annie_wangli 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

    - **device_info.hcs** configuration reference

        ```c
        root {
            device_i3c :: device {
                device0 :: deviceNode {
                    policy = 0;
                    priority = 52;
                    permission = 0644;
                    serviceName = "HDF_PLATFORM_I3C_MANAGER";
                    moduleName = "HDF_PLATFORM_I3C_MANAGER";
                }
            }
            i3c_virtual :: deviceNode {
                policy = 0;        // The value 0 indicates that no service is published.
                priority = 56;     // Driver startup priority.
                permission = 0644; // Permission to create device nodes for the driver.
                moduleName = "virtual_i3c_driver"; // (Mandatory) Driver name, which must be the same as moduleName in the driver entry.
                serviceName = "VIRTUAL_I3C_DRIVER"; // (Mandatory) Unique name of the service published by the driver.
                deviceMatchAttr = "virtual_i3c"; // (Mandatory) Controller private data, which must be same as that of the corresponding controller in i3c_config.hcs.
            }                                             // The specific controller information is in i3c_config.hcs.
        }
        ```

A
annie_wangli 已提交
130
    - **i3c_config.hcs** configuration reference
A
annie_wangli 已提交
131 132 133 134 135 136

        ```c
        root {
            platform {
                i3c_config {
                    match_attr = "virtual_i3c"; // (Mandatory) The value must be the same as that of deviceMatchAttr in device_info.hcs.
A
annie_wangli 已提交
137
                    template i3c_controller {    // Template configuration. In the template, you can configure the common parameters shared by device nodes.
A
annie_wangli 已提交
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
                        busId = 0;               // (Mandatory) I3C bus number.
                        busMode = 0x0;          // Bus mode. Which can be 0x0 (pure), 0x1 (mixed-fast), 0x2 (mixed-limited), or 0x3 (mixed-slow).
                        regBasePhy = 0x120b0000; // (Mandatory) Physical base address.
                        regSize = 0xd1;        // (Mandatory) Register bit width.
                        IrqNum = 20;           // (Mandatory) Interrupt request (IRQ) number.
                        i3cMaxRate = 12900000;  // (Optional) Maximum clock rate in I3C mode.
                        i3cRate = 12500000;      // (Optional) Clock rate in I3C mode.
                        i2cFmRate = 1000000;    // (Optional) Clock rate in I2C FM mode.
                        i2cFmPlusRate = 400000; // (Optional) Clock rate in I2C FM+ mode.
                    }
                    controller_0 :: i3c_controller {
                        busId = 18;
                        IrqNum = 20;
                    }
                }
            }
        }
        ```

A
annie_wangli 已提交
157
3. Initialize the **I3cCntlr** object at the core layer, including initializing the custom structure (passing parameters and data) and instantiating **I3cMethod** (used to call the underlying functions of the driver) in **I3cCntlr**.
A
annie_wangli 已提交
158 159 160 161 162

    The **HdfDriverEntry** member functions (**Bind**, **Init**, and **Release**) must be implemented in this step.

    - Custom structure reference
      
A
annie_wangli 已提交
163
        > The custom structure holds parameters and data for the driver. The HDF reads the values in the **i3c_config.hcs** file and initializes the structure members through **DeviceResourceIface**. Some important values, such as the device number and bus number, are also passed to the **I3cCntlr** object at the core layer.
A
annie_wangli 已提交
164 165 166
    
        ```c
        struct VirtualI3cCntlr {
A
annie_wangli 已提交
167
            struct I3cCntlr cntlr;   // (Mandatory) Control object at the core layer. For details, see the following description of I3cCntlr.
A
annie_wangli 已提交
168
            volatile unsigned char *regBase;// (Mandatory) Register base address.
A
annie_wangli 已提交
169
            uint32_t regBasePhy;     // (Mandatory) Physical base address of the register.
A
annie_wangli 已提交
170 171 172 173 174 175 176 177 178
            uint32_t regSize;        // (Mandatory) Bit width of the register.
            uint16_t busId;          // (Mandatory) Device number.
            uint16_t busMode;
            uint16_t IrqNum;
            uint32_t i3cMaxRate;
            uint32_t i3cRate;
            uint32_t i2cFmRate;
            uint32_t i2cFmPlusRate;
        };
A
annie_wangli 已提交
179
        
A
annie_wangli 已提交
180 181 182 183 184 185 186 187 188 189 190 191 192 193
        /* I3cCntlr is the controller structure at the core layer. Its members are assigned with values by using the Init function.
        struct I3cCntlr {
            OsalSpinlock lock;
            void *owner;
            int16_t busId;
            struct I3cConfig config;
            uint16_t addrSlot[(I3C_ADDR_MAX + 1) / ADDRS_PER_UINT16];
            struct I3cIbiInfo *ibiSlot[I3C_IBI_MAX];
            const struct I3cMethod *ops;
            const struct I3cLockMethod *lockOps;
            void *priv;
        };
        ```

A
annie_wangli 已提交
194
        > **(Important)** This example does not provide the instantiation of the **I3cLockMethod** callback structure in **I3cCntlr**. For details, see the I2C driver development. Other members are initialized in the **Init** function.
A
annie_wangli 已提交
195 196

    
A
annie_wangli 已提交
197
    - **Init** function
A
annie_wangli 已提交
198
    
A
annie_wangli 已提交
199 200
        > Input parameter:
        >  **HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs configuration.
A
annie_wangli 已提交
201
        > 
A
annie_wangli 已提交
202 203
        > Return value:
        > **HDF_STATUS** (The following table lists some states. For more details, see **HDF\_STATUS** definition in the **/drivers/framework/include/utils/hdf\_base.h file**.)
A
annie_wangli 已提交
204
        
A
annie_wangli 已提交
205 206
        |State|Description|
        |:-|:--|
A
annie_wangli 已提交
207 208 209 210 211 212 213
        |HDF_ERR_INVALID_OBJECT|Invalid controller object.|
        |HDF_ERR_INVALID_PARAM |Invalid parameter.|
        |HDF_ERR_MALLOC_FAIL   |Failed to allocate memory.|
        |HDF_ERR_IO            |I/O error.|
        |HDF_SUCCESS           |Transmission successful.|
        |HDF_FAILURE           |Transmission failed.|
    
A
annie_wangli 已提交
214 215
        > Function description:
        > Initializes the custom structure object and **I3cCntlr**, and calls the **I3cCntlrAdd** function to add the I3C controller to the core layer.
A
annie_wangli 已提交
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247

        ```c
        static int32_t VirtualI3cParseAndInit(struct HdfDeviceObject *device, const struct DeviceResourceNode *node)
        {
            int32_t ret;
            struct VirtualI3cCntlr *virtual = NULL;    // (Mandatory) Custom structure object.
            (void)device;
    
            virtual = (struct VirtualI3cCntlr *)OsalMemCalloc(sizeof(*virtual)); // (Mandatory) Allocate memory.
            if (virtual == NULL) {
                HDF_LOGE("%s: Malloc virtual fail!", __func__);
                return HDF_ERR_MALLOC_FAIL;
            }
    
            ret = VirtualI3cReadDrs(virtual, node);     // (Mandatory) Fill the default values defined in the i3c_config file to the structure.
            if (ret != HDF_SUCCESS) {
                HDF_LOGE("%s: Read drs fail! ret:%d", __func__, ret);
                goto __ERR__;
            }
            ...
            virtual->regBase = OsalIoRemap(virtual->regBasePhy, virtual->regSize);// (Mandatory) Address mapping.
            ret = OsalRegisterIrq(hi35xx->softIrqNum, OSAL_IRQF_TRIGGER_NONE, I3cIbiHandle, "I3C", virtual); // (Mandatory) Register the interrupt handler.
            if (ret != HDF_SUCCESS) {
                HDF_LOGE("%s: register irq failed!", __func__);
                return ret;
            }
            ...
            VirtualI3cCntlrInit(virtual);              // (Mandatory) Initialize the I3C device.
            virtual->cntlr.priv = (void *)node;        // (Mandatory) Set the storage device attributes.
            virtual->cntlr.busId = virtual->busId;     // (Mandatory) Initialize I3cCntlr.
            virtual->cntlr.ops = &g_method;           // (Mandatory) Connect to the I3cMethod instance. 
            (void)OsalSpinInit(&virtual->spin);
A
annie_wangli 已提交
248
            ret = I3cCntlrAdd(&virtual->cntlr);        // (Mandatory) Call this function to add the controller to the core layer. If a success signal is returned, the driver is completely connected to the core layer.
A
annie_wangli 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
            if (ret != HDF_SUCCESS) {
                HDF_LOGE("%s: add i3c controller failed! ret = %d", __func__, ret);
                (void)OsalSpinDestroy(&virtual->spin);
                goto __ERR__;
            }

            return HDF_SUCCESS;
        __ERR__:                                       // If the controller fails to be added, deinitialize related functions.
            if (virtual != NULL) {
                OsalMemFree(virtual);
                virtual = NULL;
            }

            return ret;
        }
    
        static int32_t VirtualI3cInit(struct HdfDeviceObject *device)
        {
            int32_t ret;
            const struct DeviceResourceNode *childNode = NULL;

            if (device == NULL || device->property == NULL) {
                HDF_LOGE("%s: device or property is NULL", __func__);
                return HDF_ERR_INVALID_OBJECT;
            }

            DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
                ret = VirtualI3cParseAndInit(device, childNode);
                if (ret != HDF_SUCCESS) {
                    break;
                }
            }

            return ret;
        }
        ```

A
annie_wangli 已提交
286
    - **Release** function
A
annie_wangli 已提交
287

A
annie_wangli 已提交
288 289
        > Input parameter:
        > **HdfDeviceObject**, an interface parameter exposed by the driver, contains the .hcs configuration.
A
annie_wangli 已提交
290
        > 
A
annie_wangli 已提交
291
        > Return value:
A
annie_wangli 已提交
292 293
        > None.
        > 
A
annie_wangli 已提交
294 295
        > Function description:
        > Releases the memory and deletes the controller. This function assigns a value to the **Release** function in the driver entry structure. If the HDF fails to call the **Init** function to initialize the driver, the **Release** function can be called to release driver resources. All forced conversion operations for obtaining the corresponding object can be successful only when the **Init** function has the corresponding value assignment operations.
A
annie_wangli 已提交
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
    
        ```c
        static void VirtualI3cRemoveByNode(const struct DeviceResourceNode *node)
        {
            int32_t ret;
            int16_t busId;
            struct I3cCntlr *cntlr = NULL;
            struct VirtualI3cCntlr *virtual = NULL;
            struct DeviceResourceIface *drsOps = NULL;
        
            drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
            if (drsOps == NULL || drsOps->GetUint32 == NULL) {
                HDF_LOGE("%s: invalid drs ops fail!", __func__);
                return;
            }
        
            ret = drsOps->GetUint16(node, "busId", (uint16_t *)&busId, 0);
            if (ret != HDF_SUCCESS) {
                HDF_LOGE("%s: read busId fail!", __func__);
                return;
            }
        ...
        /* Call the I3cCntlrGet function to obtain the I3cCntlr object through the cntlrNum of the device, and call the I3cCntlrRemove function to release the I3cCntlr object. */
            cntlr = I3cCntlrGet(busId);
            if (cntlr != NULL && cntlr->priv == node) {
                I3cCntlrPut(cntlr);
                I3cCntlrRemove(cntlr);                    // (Mandatory) Remove the I3cCntlr object from the manager driver.
A
annie_wangli 已提交
323
                virtual = (struct VirtualI3cCntlr *)cntlr; // (Mandatory) Obtain the custom object through a forced conversion and perform the release operation.
A
annie_wangli 已提交
324 325 326 327 328 329 330 331 332
                (void)OsalSpinDestroy(&virtual->spin);
                OsalMemFree(virtual);
            }
            return;
        }
        
        static void VirtualI3cRelease(struct HdfDeviceObject *device)
        {
            const struct DeviceResourceNode *childNode = NULL;
A
annie_wangli 已提交
333
        
A
annie_wangli 已提交
334
            HDF_LOGI("%s: enter", __func__);
A
annie_wangli 已提交
335
        
A
annie_wangli 已提交
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
            if (device == NULL || device->property == NULL) {
                HDF_LOGE("%s: device or property is NULL", __func__);
                return;
            }
        ...
        // Traverse and parse all nodes in i3c_config.hcs and perform the release operation on each node.
            DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode) {
                VirtualI3cRemoveByNode(childNode);  // See the description of VirtualI3cRemoveByNode for more details.
            }
        }
        ```

4. Implement the interrupt handler. The interrupt handler helps implement operations such as hot-join and IBI based on the address of the interrupt generated.

    ```c
    static int32_t VirtualI3cReservedAddrWorker(struct VirtualI3cCntlr *virtual, uint16_t addr)
    {
        (void)virtual;
        switch (addr) {
            case I3C_HOT_JOIN_ADDR:
                 VirtualI3cHotJoin(virtual);
                break;
            case I3C_RESERVED_ADDR_7H3E:
            case I3C_RESERVED_ADDR_7H5E:
            case I3C_RESERVED_ADDR_7H6E:
            case I3C_RESERVED_ADDR_7H76:
            case I3C_RESERVED_ADDR_7H7A:
            case I3C_RESERVED_ADDR_7H7C:
            case I3C_RESERVED_ADDR_7H7F:
            /* All single-bit errors in the broadcast address */
            HDF_LOGW("%s: broadcast Address single bit error!", __func__);
                break;
            default:
                HDF_LOGD("%s: Reserved address which is not supported!", __func__);
                break;
        }
        
        return HDF_SUCCESS;
    }
    ```

    ```c
    static int32_t I3cIbiHandle(uint32_t irq, void *data)
    {
        struct VirtualI3cCntlr *virtual = NULL;
        struct I3cDevice *device = NULL;
        uint16_t ibiAddr;
        char *testStr = "Hello I3C!";
A
annie_wangli 已提交
384
    
A
annie_wangli 已提交
385 386 387 388 389 390
        (void)irq;
        if (data == NULL) {
            HDF_LOGW("%s: data is NULL!", __func__);
            return HDF_ERR_INVALID_PARAM;
        }
        virtual = (struct VirtualI3cCntlr *)data;
A
annie_wangli 已提交
391
        /* (Mandatory) Obtain the address where the interrupt is generated. Use the CHECK_RESERVED_ADDR macro to determine whether the address is a reserved address of the I3C. */
A
annie_wangli 已提交
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
        ibiAddr = VirtualI3cGetIbiAddr();
        if (CHECK_RESERVED_ADDR(ibiAddr) == I3C_ADDR_RESERVED) {
            HDF_LOGD("%s: Calling VirtualI3cResAddrWorker...", __func__);
            return VirtualI3cReservedAddrWorker(virtual, ibiAddr);
        } else {
            HDF_LOGD("%s: Calling I3cCntlrIbiCallback...", __func__);
            device = GetDeviceByAddr(&virtual->cntlr, ibiAddr);
            if (device == NULL) {
                HDF_LOGE("func:%s device is NULL!",__func__);
                return HDF_ERR_MALLOC_FAIL;
            }
            if (device->ibi->payload > VIRTUAL_I3C_TEST_STR_LEN) {
                /* Place the string "Hello I3C!" into the IBI buffer. */
                *device->ibi->data = *testStr;
            }
            /* Invoke the IBI callback based on the I3C device that generates the IBI. */
            return I3cCntlrIbiCallback(device);
        }
A
annie_wangli 已提交
410
    
A
annie_wangli 已提交
411 412 413
        return HDF_SUCCESS;
    }
    ```