xref: /aosp_15_r20/external/intel-media-driver/media_common/agnostic/common/hw/mhw_block_manager.h (revision ba62d9d3abf0e404f2022b4cd7a85e107f48596f)
1 /*
2 * Copyright (c) 2015-2017, Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
18 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 * OTHER DEALINGS IN THE SOFTWARE.
21 */
22 //!
23 //! \file      mhw_block_manager.h
24 //! \brief         This modules implements state heap block manager used in MHW
25 //!
26 #ifndef __MHW_BLOCK_MANAGER_H__
27 #define __MHW_BLOCK_MANAGER_H__
28 
29 #include "mos_os.h"
30 #include "mhw_state_heap.h"
31 #include "mhw_memory_pool.h"
32 
33 #define BLOCK_MANAGER_CHK_STATUS(_stmt)                                               \
34     MOS_CHK_STATUS_RETURN(MOS_COMPONENT_HW, MOS_HW_SUBCOMP_ALL, _stmt)
35 
36 #define BLOCK_MANAGER_CHK_STATUS_MESSAGE(_stmt, _message, ...)                        \
37     MOS_CHK_STATUS_MESSAGE_RETURN(MOS_COMPONENT_HW, MOS_HW_SUBCOMP_ALL, _stmt, _message, ##__VA_ARGS__)
38 
39 #define BLOCK_MANAGER_CHK_NULL(_ptr)                                                  \
40     MOS_CHK_NULL_RETURN(MOS_COMPONENT_HW, MOS_HW_SUBCOMP_ALL, _ptr)
41 
42 #define MHW_BLOCK_MANAGER_INVALID_TAG ((uint32_t)-1)
43 
44 // Maximum allocation array size
45 #define MHW_BLOCK_MANAGER_MAX_BLOCK_ARRAY  64
46 
47 typedef struct _MHW_BLOCK_MANAGER_PARAMS
48 {
49     uint32_t                     dwPoolInitialCount;     //!< Initial number of memory blocks in pool
50     uint32_t                     dwPoolMaxCount;         //!< Maximum number of memory blocks in pool
51     uint32_t                     dwPoolIncrement;        //!< Memory block pool increment
52     uint32_t                     dwHeapInitialSize;      //!< Initial heap size
53     uint32_t                     dwHeapIncrement;        //!< Heap size increment
54     uint32_t                     dwHeapMaxSize;          //!< Maximum overall heap size
55     uint32_t                     dwHeapMaxCount;         //!< Maximum number of heaps
56     uint32_t                     dwHeapGranularity;      //!< Block granularity
57     uint32_t                     dwHeapBlockMinSize;     //!< Minimum block fragment size (never create a block smaller than this)
58 } MHW_BLOCK_MANAGER_PARAMS, *PMHW_BLOCK_MANAGER_PARAMS;
59 
60 #define MHW_BLOCK_POSITION_TAIL (NULL)
61 #define MHW_BLOCK_POSITION_HEAD ((PMHW_STATE_HEAP_MEMORY_BLOCK) -1)
62 
63 typedef struct _MHW_BLOCK_LIST
64 {
65     PMHW_BLOCK_MANAGER              pBlockManager;      //!< Block Manager the list belongs to
66     PMHW_STATE_HEAP_MEMORY_BLOCK    pHead;              //!< Head of the list
67     PMHW_STATE_HEAP_MEMORY_BLOCK    pTail;              //!< Tail of the list
68     MHW_BLOCK_STATE                 BlockState;         //!< Described the type of block the list contains
69     int32_t                         iCount;             //!< Number of elements on the list
70     uint32_t                        dwSize;             //!< Total memory in the list
71     char                            szListName[16];     //!< List name for debugging purposes
72 } MHW_BLOCK_LIST, *PMHW_BLOCK_LIST;
73 
74 struct MHW_BLOCK_MANAGER
75 {
76 private:
77     MHW_BLOCK_MANAGER_PARAMS m_Params;                              //!< Block Manager configuration
78     MHW_MEMORY_POOL          m_MemoryPool;                          //!< Memory pool of PMHW_STATE_HEAP_MEMORY_BLOCK objects
79     MHW_BLOCK_LIST           m_BlockList[MHW_BLOCK_STATE_COUNT];    //!< Block lists associated with each block state
80     PMHW_STATE_HEAP          m_pStateHeap;                          //!< Points to state heap
81 
82 public:
83 
84     //!
85     //! \brief    Initializes the MHW_BLOCK_MANAGER
86     //! \details  Constructor of MHW_BLOCK_MANAGER which initializes all parameters and members
87     //! \param    [in] pParams
88     //!           Pointer to block manager params. If it is null, default param will be used.
89     //!
90     MHW_BLOCK_MANAGER(PMHW_BLOCK_MANAGER_PARAMS pParams);
91 
92     //!
93     //! \brief    Destructor of MHW_BLOCK_MANAGER
94     ~MHW_BLOCK_MANAGER();
95 
96     //!
97     //! \brief    Adds newly created state heap to block manager
98     //! \details  Adds a new state heap to memory block manager. A new memory free memory block is added to
99     //!           the free list representing the new available heap.
100     //! \param    [in] pStateHeap
101     //!           Pointer to the newly created state heap
102     //! \return   MOS_STATUS
103     //!           Returns the status of the operation
104     //!
105     MOS_STATUS RegisterStateHeap(PMHW_STATE_HEAP    pStateHeap);
106 
107     //!
108     //! \brief    Unregisters state heap from block manager prior to deallocation
109     //! \details  Removes state heap from block manager. Memory blocks must be all deleted.
110     //! \param    [in] pStateHeap
111     //!           Pointer to the newly created state heap
112     //! \return   MOS_STATUS
113     //!           Returns the status of the operation
114     //!
115     MOS_STATUS UnregisterStateHeap(PMHW_STATE_HEAP    pStateHeap);
116 
117     //!
118     //! \brief    Update block states based on last executed tag
119     //! \details  Update block states based on last executed tag
120     //!           submitted unlocked blocks are released;
121     //!           move to allocated
122     //! \param    [in] dwSyncTag
123     //!           sync tag
124     //! \return   MOS_STATUS
125     //!           Returns the status of the operation
126     //!
127     MOS_STATUS Refresh();
128 
129     //!
130     //! \brief    Allocate memory block with scratch space
131     //! \details  Allocate memory block with scratch space
132     //! \param    [in] dwSize
133     //!           Size of memory block to allocate
134     //! \param    [in] dwAlignment
135     //!           Alignment
136     //! \param    [in] dwScratchSpace
137     //!           Scratch space size
138     //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
139     //!           Pointer to the allocated memory block
140     //!
141     PMHW_STATE_HEAP_MEMORY_BLOCK AllocateWithScratchSpace(
142         uint32_t            dwSize,
143         uint32_t            dwAlignment,
144         uint32_t            dwScratchSpace);
145 
146     //!
147     //! \brief    Allocate free block of memory
148     //! \details  Allocate free memory block of memory for use by the client
149     //! \param    [in] dwSize
150     //!           Size of memory to be allocated
151     //! \param    [in] dwAlignment
152     //!           Memory alignment
153     //! \param    [in] pHeapAffinity
154     //!           Pointer to heap affinity
155     //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
156     //!           Pointer to allocated memory block
157     //!
158     PMHW_STATE_HEAP_MEMORY_BLOCK AllocateBlock(
159         uint32_t            dwSize,
160         uint32_t            dwAlignment,
161         PMHW_STATE_HEAP     pHeapAffinity);
162 
163     //!
164     //! \brief    Free memory block
165     //! \details  Free memory block according to the sync tag
166     //! \param    [in] pBlock
167     //!           Pointer to memory block to be freed
168     //! \param    [in] dwSyncTag
169     //!           Sync tag
170     //! \return   MOS_STATUS
171     //!           Returns the status of the operation
172     //!
173     MOS_STATUS FreeBlock(
174         PMHW_STATE_HEAP_MEMORY_BLOCK pBlock);
175 
176     //!
177     //! \brief    Calculate the memory of space required
178     //! \details  Calculate the memory of space required
179     //! \param    [in] pdwSizes
180     //!           Pointer to memory block to be freed
181     //! \param    [in] iCount
182     //!           Sync tag
183     //! \param    [in] dwAlignment
184     //!           alignment
185     //! \param    [in] bHeapAffinity
186     //!           heap affinity
187     //! \param    [in] pHeapAffinity
188     //!           pointer to state heap affinity
189     //! \return   uint32_t Size of space needed by client
190     //!
191     uint32_t CalculateSpaceNeeded(
192         const uint32_t      *pdwSizes,
193         int32_t             iCount,
194         uint32_t            dwAlignment,
195         bool                bHeapAffinity,
196         PMHW_STATE_HEAP     pHeapAffinity);
197 
198     //!
199     //! \brief    Submit memory block
200     //! \details  Submit memory block according to the sync tag
201     //! \param    [in] pBlock
202     //!           Pointer to memory block to be submitted
203     //! \param    [in] dwSyncTag
204     //!           Sync tag
205     //! \return   MOS_STATUS
206     //!           Returns the status of the operation
207     //!
208     MOS_STATUS SubmitBlock(
209         PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
210         const FrameTrackerTokenFlat  *trackerToken);
211 
212     //!
213     //! \brief    Set state heap to block manager
214     //! \details  Set state heap to block manager
215     //! \param    [in] pStateHeap
216     //!           Pointer to state heap
217     //!
218     void SetStateHeap(PMHW_STATE_HEAP pStateHeap);
219 
220     // Multiple Block Allocation algorithm description (multiple kernel load):
221     //
222     // Multiple blocks must be efficiently allocated in multiple heaps or in a single heap.
223     //
224     // Each load may introduce block fragmentation. The more kernels are loaded, the greater
225     // are the chances to fragment the heap, reducing the chances of finding contiguous for a
226     // larger kernel. Therefore, the efficient way to use the heap is to load larger kernels
227     // first ensuring they have enough contiguous space, and then load the smaller kernels,
228     // which are more likely to fit in remaining blocks.
229     //
230     // So the algorithm is the following:
231     //
232     // 1) The total size of all blocks is calculated to check if there's even a fighting chance
233     //    to load them all - if the amount available is insufficient, other measures will be taken.
234     //
235     // 2) In order to allocate blocks according to size, we first sort the array of sizes
236     //    using an index array (never touching the source array!) - this is done by a merge sort
237     //    implementation, which is O(n*log(n)) - may try using other algorithm such as QuickSort -
238     //    although quicksort is not always the best (may be O(n^2) in same cases)
239     //
240     // 3) Select a specific heap (or nullptr if bHeapAffinity is false). Check if there is enough space
241     //    to load all blocks - ignoring the fragmentation for now. Only by traversing the list of
242     //    blocks we will be able to determine if the heap can be used or not. Trying to load larger
243     //    kernels FIRST helps identifying heap fragmentation issues faster than leaving it for last.
244     //
245     // 4) Load the array of kernels one at a time according to size (larger first). Select a specific
246     //    heap or nullptr (if any heap, don't care).
247     //
248     // 5) If all succeeded, then we are ready to return the list of blocks to the client.
249     //
250     // 6) If it fails, release the already allocated blocks. the algorithm does check for space in the
251     //    current heap before even starting the loop. The failure will not happen for lack of space,
252     //    but because of fragmentation of free space. In case of bHeapAffinity = false, we also check
253     //    the aggregate free space.
254     //
255     // 7) If the heap affinity is selected and the intended heap is also provided, we're done - if we
256     //    were not able to load the blocks, the overall operation has failed. If affinity is selected but
257     //    the heap was not provided, we try the next heap until no more heaps are available. This particular
258     //    situation is intended for loading all blocks in the same heap without specifying which one.
259     //
260 
261     //!
262     //! \brief    Allocate multiple blocks
263     //! \details  Allocate multiple blocks
264     //! \param    [in]  pdwSizes
265     //!           Pointer to sizes
266     //! \param    [in]  iCount
267     //!           Count of blocks to be allocated
268     //! \param    [in] dwAlignment
269     //!           Alignment
270     //! \param    [in] bHeapAffinity
271     //!           true if all blocks must be allocated in the same heap
272     //! \param    [in] pHeapAffinity
273     //!           Pointer to heap affinity
274     //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
275     //!           Pointer to allocated memory block
276     //!
277     PMHW_STATE_HEAP_MEMORY_BLOCK AllocateMultiple(
278         uint32_t            *pdwSizes,
279         int32_t             iCount,
280         uint32_t            dwAlignment,
281         bool                bHeapAffinity,
282         PMHW_STATE_HEAP     pHeapAffinity);
283 
284 private:
285 
286     //!
287     //! \brief    Gets memory block from pool
288     //! \details  Gets memory block from pool, extending the pool if necessary
289     //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
290     //!           Returns a pointer to the memory block, nullptr if failed to allocate or
291     //!           all blocks are in use and the pool reached its maximum size.
292     //!
293     PMHW_STATE_HEAP_MEMORY_BLOCK GetBlockFromPool();
294 
295     //!
296     //! \brief    Extends pool of MHW_STATE_HEAP_MEMORY_BLOCK objects
297     //! \details  Allocates an array of MHW_STATE_HEAP_MEMORY_BLOCK objects and appends to the pool of objects for reuse.
298     //!           The allocation of memory block structures is limited to the maximum number defined when creating
299     //!           the Memory Block Manager object. The increase in number of memory blocks may be due to increased
300     //!           block fragmentation, system load or other factors (increase in queue depth, increas in memory
301     //!           blocks maintained by the client - such as number of kernels loaded).
302     //! \param    [in] dwCount
303     //!           Number of additional blocks to add to the pool
304     //! \return   N/A
305     //!
306     void ExtendPool(uint32_t dwCount);
307 
308     //!
309     //! \brief    Attach block into appropriate memory block list
310     //! \details  Sets block state and inserts it into the memory block list associated with the new state.
311     //!           The block may be inserted at the "Head" or "Tail" of the list, or after another block from the same list.
312     //! \param    [in]BlockState
313     //!           New block state, defines which list the block is to be inserted.
314     //!           NOTE: The block must not belong to any other list before calling this function (pPrev/pNext = nullptr).
315     //! \param    [in] pBlock
316     //!           Pointer to memory block
317     //! \param    [in] pBlockPos
318     //!           Pointer to memory block after which the block is to be inserted (inserted AFTER pBlockPos)
319     //!           If set to MHW_BLOCK_POSITION_HEAD, inserts block at the head of the list.
320     //!           If set to MHW_BLOCK_POSITION_TAIL, inserts block at the tail of the list.
321     //! \return   MOS_STATUS
322     //!           Returns error code
323     //!
324     MOS_STATUS AttachBlock(
325         MHW_BLOCK_STATE              BlockState,
326         PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
327         PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);
328 
329     //!
330     //! \brief    INTERNAL function to insert a block into a block list.
331     //! \details  Insert a block into a block list at a given position (head, tail, insert after block)
332     //!           Ensures that the list and the block position are both valid.
333     //!           Does not check if the block is still attached to another list.
334     //!           IMPORTANT: Block must be detached, at the risk of corrupting other block lists.
335     //!                      This function does not track state heap usage
336     //! \param    [in] pList
337     //!           Pointer to memory block list structure
338     //! \param    [in] BlockState
339     //!           New block state, defines the state of the new block,
340     //!           must match the state associated with the list and reference block
341     //! \param    [in] pBlock
342     //!           Pointer to memory block to be inserted
343     //! \param    [in] pBlockPos
344     //!           Pointer to memory block after which the block is to be inserted (inserted AFTER pBlockPos)
345     //!           If set to MHW_BLOCK_POSITION_HEAD, inserts block at the head of the list.
346     //!           If set to MHW_BLOCK_POSITION_TAIL, inserts block at the tail of the list.
347     //! \return   MOS_STATUS
348     //!           Returns error code
349     //!
350     MOS_STATUS AttachBlockInternal(
351         PMHW_BLOCK_LIST              pList,
352         MHW_BLOCK_STATE              BlockState,
353         PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
354         PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);
355 
356     //!
357     //! \brief    Removes a block from a memory block list
358     //! \details  Removes a block from a memory block list from a given
359     //! \param    [in] BlockState
360     //!           Block state defines from which list the block is to be removed.
361     //! \param    [in] pBlockPos
362     //!           Defines the object in list to be detached.
363     //!           If set to MHW_BLOCK_POSITION_HEAD, detaches the first block in the list.
364     //!           If set to MHW_BLOCK_POSITION_TAIL, detaches the last block in the list.
365     //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
366     //!           Returns a pointer to the block detached, nullptr if the list is empty or invalid.
367     //!
368     PMHW_STATE_HEAP_MEMORY_BLOCK DetachBlock(
369         MHW_BLOCK_STATE              BlockState,
370         PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);
371 
372     //!
373     //! \brief    INTERNAL function to remove a block from a block list.
374     //! \details  Removes a block from a list provided. The caller may specify the block
375     //!           to remove, or request the head or tail of the list.
376     //!           Ensures that the list and the block position are both valid.
377     //!           Does not check if the block is still attached to another list.
378     //!           IMPORTANT: Block must be detached, at the risk of corrupting other block lists.
379     //!                      This function does not track state heap usage
380     //! \param    [in] pList
381     //!           Pointer to memory block list structure
382     //! \param    [in] pBlock
383     //!           Pointer to memory block to be inserted
384     //! \return   PMHW_STATE_HEAP_MEMORY_BLOCK
385     //!           Returns block, nullptr if failure or no block available
386     //!
387     PMHW_STATE_HEAP_MEMORY_BLOCK DetachBlockInternal(
388         PMHW_BLOCK_LIST              pList,
389         PMHW_STATE_HEAP_MEMORY_BLOCK pBlock);
390 
391     //!
392     //! \brief    Interface to move block from one list to another
393     //! \details  Moves a block from a list into another
394     //!           Ensures that the list and the block position are both valid.
395     //!           Does not check if the block is still attached to another list.
396     //!           IMPORTANT: Block must be detached, at the risk of corrupting other block lists.
397     //!                      This function does not track state heap usage
398     //! \param    [in] pSrcList
399     //!           Pointer to source list
400     //! \param    [in] pDstList
401     //!           Pointer to destination list
402     //! \param    [in] pBlock
403     //!           Pointer to memory block to be moved
404     //! \param    [in] pBlockPos
405     //!           Pointer to memory block after which the block is to be inserted (inserted AFTER pBlockPos)
406     //!           If set to MHW_BLOCK_POSITION_HEAD, inserts block at the head of the list.
407     //!           If set to MHW_BLOCK_POSITION_TAIL, inserts block at the tail of the list.
408     //! \return   MOS_STATUS
409     //!           Returns error code
410     //!
411     MOS_STATUS MoveBlock(
412         PMHW_BLOCK_LIST              pSrcList,      // Source list
413         PMHW_BLOCK_LIST              pDstList,      // Destination list
414         PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,        // Block to be moved (or HEAD/TAIL of source list)
415         PMHW_STATE_HEAP_MEMORY_BLOCK pBlockPos);    // Position to insert (or HEAD/TAIL of target list)
416 
417     //!
418     //! \brief    Returns memory block object to pool
419     //! \details  Returns memory block object to pool
420     //! \param    [in] pBlock
421     //!           Pointer to memory block to be returned back to pool
422     //! \return   N/A
423     //!
424     void ReturnBlockToPool(PMHW_STATE_HEAP_MEMORY_BLOCK pBlock);
425 
426     //!
427     //! \brief    Consolidate free memory
428     //! \details  Consolidate free memory blocks adjacent to a given free block (within the same state heap).
429     //! \param    [in] pBlock
430     //!           Pointer to free memory block
431     //! \return    N/A
432     //!
433     void ConsolidateBlock(PMHW_STATE_HEAP_MEMORY_BLOCK pBlock);
434 
435     //!
436     //! \brief    INTERNAL: Move block from free to allocated list
437     //! \details  Move block from free to allocated list, setting up pointer/offset to data
438     //! \param    [in] pBlock
439     //!           Pointer to free memory block
440     //! \param    uint32_t dwAlignment
441     //!           [in] Data alignment, assured to be power of 2 by caller
442     //! \return   MOS_STATUS
443     //!           Returns the status of the operation
444     //!
445     MOS_STATUS AllocateBlockInternal(
446         PMHW_STATE_HEAP_MEMORY_BLOCK pBlock,
447         uint32_t                     dwAlignment);
448 
449     //!
450     //! \brief    Split block internal
451     //! \details  Split block internal
452     //! \param    [in] pBlock
453     //!           Pointer to block to be splitted
454     //! \param    [in] dwSplitSize
455     //!           Size of the block to be splitted
456     //! \param    [in] dwAlignment
457     //!           Data alignment, assured to be power of 2 by caller
458     //! \param    [in] bBackward
459     //!           if true, use higher part of the block
460     //! \return   MOS_STATUS
461     //!           Returns the status of the operation
462     //!
463     MOS_STATUS SplitBlockInternal(
464         PMHW_STATE_HEAP_MEMORY_BLOCK    pBlock,
465         uint32_t                        dwSplitSize,
466         uint32_t                        dwAlignment,
467         bool                            bBackward);
468 
469     //!
470     //! \brief    Merge blocks
471     //! \details  Merge blocks
472     //! \param    [in]  pBlockL
473     //!           Pointer to block in lower memory
474     //! \param    [in]  pBlockH
475     //!           Pointer to block in higher memory
476     //! \param    [in] dwAlignment
477     //!           Data alignment, assured to be power of 2 by caller
478     //! \param    [in] bBackward
479     //!           if True, pBlcokL is merged into pBlockH
480     //!           if False, pBlockH is merged into pBlockL
481     //! \return   MOS_STATUS
482     //!           Returns the status of the operation
483     //!
484     MOS_STATUS MergeBlocksInternal(
485         PMHW_STATE_HEAP_MEMORY_BLOCK    pBlockL,
486         PMHW_STATE_HEAP_MEMORY_BLOCK    pBlockH,
487         uint32_t                        dwAlignment,
488         bool                            bBackward);
489 
490     //!
491     //! \brief    ResizeBlock
492     //! \details  ResizeBlock
493     //! \param    [in] pBlock
494     //!           Pointer to block to be resized
495     //! \param    [in]   dwNewSize
496     //!           new size of block
497     //! \param    [in] dwAlignment
498     //!           Data alignment, assured to be power of 2 by caller
499     //! \param    [in] bBackward
500     //!           if True,  allow block to grow forward/backwards (moving its start offset)
501     //!           if False, Always grow/shrink forward;
502     //! \return   MOS_STATUS
503     //!           Returns the status of the operation
504     //!
505     MOS_STATUS ResizeBlock(
506         PMHW_STATE_HEAP_MEMORY_BLOCK    pBlock,
507         uint32_t                        dwNewSize,
508         uint32_t                        dwAlignment,
509         bool                            bBackward);
510 };
511 
512 #endif // __MHW_BLOCK_MANAGER_H__
513