ReactOS 0.4.16-dev-297-gc569aee
stream.cpp
Go to the documentation of this file.
1// Every debug output has "Modulname text"
2#define STR_MODULENAME "AC97 Stream: "
3
4#include "shared.h"
5#include "miniport.h"
6
7#ifdef _MSC_VER
8#pragma code_seg("PAGE")
9#endif
10
11
12/*****************************************************************************
13 * CMiniportStream::NonDelegatingQueryInterface
14 *****************************************************************************
15 * Obtains an interface. This function works just like a COM QueryInterface
16 * call and is used if the object is not being aggregated.
17 */
19(
22 _In_ REFIID iStream,
24)
25{
26 PAGED_CODE ();
27
28 ASSERT (Object);
29
30 DOUT (DBG_PRINT, ("[CMiniportStream::NonDelegatingQueryInterface]"));
31
32 //
33 // Convert for IID_IMiniportXXXStream
34 //
35 if (IsEqualGUIDAligned (Interface, iStream))
36 {
38 }
39 //
40 // Convert for IID_IDrmAudioStream
41 //
42 else if (IsEqualGUIDAligned (Interface, IID_IDrmAudioStream))
43 {
45 }
46 //
47 // Convert for IID_IUnknown
48 //
50 {
52 }
53 else
54 {
55 *Object = NULL;
57 }
58
59 ((PUNKNOWN)*Object)->AddRef ();
60 return STATUS_SUCCESS;
61}
62
63
65(
66 IN CMiniport *Miniport_,
67 IN PUNKNOWN PortStream_,
68 IN WavePins Pin_,
69 IN BOOLEAN Capture_,
70 IN PKSDATAFORMAT DataFormat_,
71 OUT PSERVICEGROUP *ServiceGroup_
72)
73{
74 PAGED_CODE ();
75
76 DOUT (DBG_PRINT, ("[CMiniportStream::Init]"));
77
78 ASSERT (Miniport_);
79 ASSERT (DataFormat_);
80
81 //
82 // The rule here is that we return when we fail without a cleanup.
83 // The destructor will relase the allocated memory.
84 //
85 NTSTATUS ntStatus = STATUS_SUCCESS;
86
87 //
88 // Save miniport pointer and addref it.
89 //
90 obj_AddRef(Miniport_, (PVOID *)&Miniport);
91
92 //
93 // Save portstream interface pointer and addref it.
94 //
95 obj_AddRef(PortStream_, (PVOID *)&PortStream);
96
97
98 //
99 // Save channel ID and capture flag.
100 //
101 Pin = Pin_;
102 Capture = Capture_;
103
104 //
105 // Save data format and current sample rate.
106 //
108 CurrentRate = DataFormat->WaveFormatEx.nSamplesPerSec;
109 NumberOfChannels = DataFormat->WaveFormatEx.nChannels;
110
111
112 if (ServiceGroup_)
113 {
114
115 //
116 // Create a service group (a DPC abstraction/helper) to help with
117 // interrupts.
118 //
119 ntStatus = PcNewServiceGroup (&ServiceGroup, NULL);
120 if (!NT_SUCCESS (ntStatus))
121 {
122 DOUT (DBG_ERROR, ("Failed to create a service group!"));
123 return ntStatus;
124 }
125 }
126
127 //
128 // Store the base address of this DMA engine.
129 //
130 if (Capture)
131 {
132 //
133 // could be PCM or MIC capture
134 //
135 if (Pin == PIN_WAVEIN)
136 {
137 // Base address for DMA registers.
139 }
140 else
141 {
142 // Base address for DMA registers.
144 }
145 }
146 else // render
147 {
148 // Base address for DMA registers.
150 }
151
152 //
153 // Reset the DMA and set the BD list pointer.
154 //
155 ResetDMA ();
156
157 //
158 // Now set the requested sample rate. In case of a failure, the object
159 // gets destroyed and releases all memory etc.
160 //
161 ntStatus = SetFormat (DataFormat_);
162 if (!NT_SUCCESS (ntStatus))
163 {
164 DOUT (DBG_ERROR, ("Stream init SetFormat call failed!"));
165 return ntStatus;
166 }
167
168 //
169 // Initialize the device state.
170 //
172
173 //
174 // Call miniport specific init routine
175 //
176 ntStatus = Init_();
177 if (!NT_SUCCESS (ntStatus))
178 {
179 return ntStatus;
180 }
181
182 //
183 // Setup the Buffer Descriptor Base Address (BDBA) register.
184 //
186
187 //
188 // Pass the ServiceGroup pointer to portcls.
189 //
190 obj_AddRef(ServiceGroup, (PVOID *)ServiceGroup_);
191
192 //
193 // Store the stream pointer, it is used by the ISR.
194 //
195 Miniport->Streams[Pin/2] = this;
196
197 return STATUS_SUCCESS;
198}
199
200
202{
203 if (Miniport)
204 {
205 //
206 // Disable interrupts and stop DMA just in case.
207 //
209 {
210 WriteReg8 (X_CR, (UCHAR)0);
211
212 //
213 // Update also the topology miniport if this was the render stream.
214 //
215 if (Miniport->AdapterCommon->GetMiniportTopology () &&
216 (Pin == PIN_WAVEOUT))
217 {
218 Miniport->AdapterCommon->GetMiniportTopology ()->SetCopyProtectFlag (FALSE);
219 }
220 }
221
222 //
223 // Remove stream from miniport Streams array.
224 //
225 if (Miniport->Streams[Pin/2] == this)
226 {
227 Miniport->Streams[Pin/2] = NULL;
228 }
229 }
230
234}
235
236
237/*****************************************************************************
238 * CMiniportStream::SetContentId
239 *****************************************************************************
240 * This routine gets called by drmk.sys to pass the content to the driver.
241 * The driver has to enforce the rights passed.
242 */
243STDMETHODIMP_(NTSTATUS) CMiniportStream::SetContentId
244(
245 _In_ ULONG contentId,
246 _In_ PCDRMRIGHTS drmRights
247)
248{
249 PAGED_CODE ();
250
251 DOUT (DBG_PRINT, ("[CMiniportStream::SetContentId]"));
252
253 UNREFERENCED_PARAMETER(contentId);
254
255 //
256 // If "drmRights->DigitalOutputDisable" is set, we need to disable S/P-DIF.
257 // Currently, we don't have knowledge about the S/P-DIF interface. However,
258 // in case you expanded the driver with S/P-DIF features you need to disable
259 // S/P-DIF or fail SetContentId. If you have HW that has S/P-DIF turned on
260 // by default and you don't know how to turn off (or you cannot do that)
261 // then you must fail SetContentId.
262 //
263 // In our case, we assume the codec has no S/P-DIF or disabled S/P-DIF by
264 // default, so we can ignore the flag.
265 //
266 // Store the copyright flag. We have to disable PCM recording if it's set.
267 //
268 if (!Miniport->AdapterCommon->GetMiniportTopology ())
269 {
270 DOUT (DBG_ERROR, ("Topology pointer not set!"));
271 return STATUS_UNSUCCESSFUL;
272 }
273 else
274 {
275 Miniport->AdapterCommon->GetMiniportTopology ()->
276 SetCopyProtectFlag (drmRights->CopyProtect);
277 }
278
279 //
280 // We assume that if we can enforce the rights, that the old content
281 // will be destroyed. We don't need to store the content id since we
282 // have only one playback channel, so we are finished here.
283 //
284
285 return STATUS_SUCCESS;
286}
287
289(
290 IN POWER_STATE NewState
291)
292{
293 DOUT (DBG_PRINT, ("[CMiniportStream::PowerChangeNotify]"));
294
295 //
296 // We don't have to check the power state, that's already done by the wave
297 // miniport.
298 //
299
300 DOUT (DBG_POWER, ("Changing state to D%d.",
301 (ULONG)NewState.DeviceState - (ULONG)PowerDeviceD0));
302
303 switch (NewState.DeviceState)
304 {
305 case PowerDeviceD0:
306 //
307 // If we are coming from D2 or D3 we have to restore the registers cause
308 // there might have been a power loss.
309 //
311 {
312 PowerChangeNotify_(NewState);
313 }
314 break;
315
316 case PowerDeviceD1:
317 // Here we do nothing. The device has still enough power to keep all
318 // it's register values.
319 break;
320
321 case PowerDeviceD2:
322 case PowerDeviceD3:
323 //
324 // If we power down to D2 or D3 we might loose power, so we have to be
325 // aware of the DMA engine resetting. In that case a play would start
326 // with scatter gather entry 0 (the current index is read only).
327 // This is fine with the RT port.
328 //
329
330 PowerChangeNotify_(NewState);
331
332 break;
333 }
334
335 //
336 // Save the new state. This local value is used to determine when to
337 // cache property accesses and when to permit the driver from accessing
338 // the hardware.
339 //
340 m_PowerState = NewState.DeviceState;
341 DOUT (DBG_POWER, ("Entering D%d",
343
344}
345
346/*
347 *****************************************************************************
348 * This routine tests for proper data format (calls wave miniport) and sets
349 * or changes the stream data format.
350 * To figure out if the codec supports the sample rate, we just program the
351 * sample rate and read it back. If it matches we return happy, if not then
352 * we restore the sample rate and return unhappy.
353 * We fail this routine if we are currently running (playing or recording).
354 */
355STDMETHODIMP_(NTSTATUS) CMiniportStream::SetFormat
356(
358)
359{
360 PAGED_CODE ();
361
362 ASSERT (Format);
363
364 ULONG TempRate;
365 DWORD dwControlReg;
366
367 DOUT (DBG_PRINT, ("[CMiniportStream::SetFormat]"));
368
369 //
370 // Change sample rate when we are in the stop or pause states - not
371 // while running!
372 //
374 {
375 return STATUS_UNSUCCESSFUL;
376 }
377
378 //
379 // Ensure format falls in proper range and is supported.
380 //
382 if (!NT_SUCCESS (ntStatus))
383 return ntStatus;
384
385 //
386 // Retrieve wave format portion.
387 //
388 PWAVEFORMATPCMEX waveFormat = (PWAVEFORMATPCMEX)(Format + 1);
389
390 //
391 // Save current rate in this context.
392 //
393 TempRate = waveFormat->Format.nSamplesPerSec;
394
395 //
396 // Check if we have a codec with one sample rate converter and there are streams
397 // already open.
398 //
401 {
402 //
403 // Figure out at which sample rate the other stream is running.
404 //
405 ULONG ulFrequency;
406
407 if (Miniport->Streams[PIN_WAVEIN_OFFSET] == this)
409 else
411
412 //
413 // Check if this sample rate is requested sample rate.
414 //
415 if (ulFrequency != TempRate)
416 {
417 return STATUS_UNSUCCESSFUL;
418 }
419 }
420
421 //
422 // Program the AC97 to support n channels.
423 //
424 if (Pin == PIN_WAVEOUT)
425 {
426 dwControlReg = Miniport->AdapterCommon->ReadBMControlRegister32 (GLOB_CNT);
427 dwControlReg = (dwControlReg & 0x03F) |
428 (((waveFormat->Format.nChannels >> 1) - 1) * GLOB_CNT_PCM4);
429 Miniport->AdapterCommon->WriteBMControlRegister (GLOB_CNT, dwControlReg);
430 }
431
432 //
433 // Check for rate support by hardware. If it is supported, then update
434 // hardware registers else return not implemented and audio stack will
435 // handle it.
436 //
437 if (Capture)
438 {
439 if (Pin == PIN_WAVEIN)
440 {
441 ntStatus = Miniport->AdapterCommon->
442 ProgramSampleRate (AC97REG_RECORD_SAMPLERATE, TempRate);
443 }
444 else
445 {
446 ntStatus = Miniport->AdapterCommon->
447 ProgramSampleRate (AC97REG_MIC_SAMPLERATE, TempRate);
448 }
449 }
450 else
451 {
452 //
453 // In the playback case we might need to update several DACs
454 // with the new sample rate.
455 //
456 ntStatus = Miniport->AdapterCommon->
457 ProgramSampleRate (AC97REG_FRONT_SAMPLERATE, TempRate);
458
460 {
461 ntStatus = Miniport->AdapterCommon->
462 ProgramSampleRate (AC97REG_SURROUND_SAMPLERATE, TempRate);
463 }
464 if (Miniport->AdapterCommon->GetNodeConfig (NODEC_LFE_DAC_PRESENT))
465 {
466 ntStatus = Miniport->AdapterCommon->
467 ProgramSampleRate (AC97REG_LFE_SAMPLERATE, TempRate);
468 }
469 }
470
471 if (NT_SUCCESS (ntStatus))
472 {
473 //
474 // print information and save the format information.
475 //
477 CurrentRate = TempRate;
478 NumberOfChannels = waveFormat->Format.nChannels;
479 }
480
481 return ntStatus;
482}
483
484/*****************************************************************************
485 * Non paged code begins here
486 *****************************************************************************
487 */
488
489#ifdef _MSC_VER
490#pragma code_seg()
491#endif
492
494{
495 // get X_CR register value
496 UCHAR RegisterValue = ReadReg8(X_CR);
497 UCHAR RegisterValueNew = RegisterValue & ~CR_RPBM;
499 RegisterValueNew |= CR_RPBM;
500
501 // write X_CR register value
502 if(RegisterValue != RegisterValueNew)
503 WriteReg8(X_CR, RegisterValueNew);
504 return RegisterValueNew;
505}
506
507
509(
510 DWORD* buffPos
511)
512{
513 int nCurrentIndex;
514 DWORD RegisterX_PICB;
515
517 {
518 *buffPos = 0;
519 return 0;
520 }
521
522 //
523 // Repeat this until we get the same reading twice. This will prevent
524 // jumps when we are near the end of the buffer.
525 //
526 do
527 {
528 nCurrentIndex = ReadReg8(X_CIV);
529
530 RegisterX_PICB = ReadReg16 (X_PICB);
531 } while (nCurrentIndex != (int)ReadReg8(X_CIV));
532
533 *buffPos = (BDList[nCurrentIndex].wLength - RegisterX_PICB) * 2;
534 return nCurrentIndex;
535}
536
537/*****************************************************************************
538 * CMiniportStream::ResetDMA
539 *****************************************************************************
540 * This routine resets the Run/Pause bit in the control register. In addition, it
541 * resets all DMA registers contents.
542
543 */
545{
546 DOUT (DBG_PRINT, ("ResetDMA"));
547
548 //
549 // Turn off DMA engine (or make sure it's turned off)
550 //
552 UCHAR RegisterValue = UpdateDMA();
553
554 //
555 // Reset all register contents.
556 //
557 RegisterValue |= CR_RR;
558 WriteReg8(X_CR, RegisterValue);
559
560 //
561 // Wait until reset condition is cleared by HW; should not take long.
562 //
563 ULONG count = 0;
564 BOOL bTimedOut = TRUE;
565 do
566 {
567 if (!(ReadReg8(X_CR) & CR_RR))
568 {
569 bTimedOut = FALSE;
570 break;
571 }
573 } while (count++ < 10);
574
575 if (bTimedOut)
576 {
577 DOUT (DBG_ERROR, ("ResetDMA TIMEOUT!!"));
578 }
579
580 //
581 // We only want interrupts upon completion.
582 //
583 RegisterValue = CR_IOCE | CR_LVBIE;
584 WriteReg8(X_CR, RegisterValue);
585
586 //
587 // Setup the Buffer Descriptor Base Address (BDBA) register.
588 //
590}
591
592/*****************************************************************************
593 * CMiniportStream::ResumeDMA
594 *****************************************************************************
595 * This routine sets the Run/Pause bit for the particular DMA engine to resume
596 * it after it's been paused. This assumes that DMA registers content have
597 * been preserved.
598 */
600{
601 DOUT (DBG_PRINT, ("ResumeDMA"));
602
604 UpdateDMA();
605}
606
607/*****************************************************************************
608 * CMiniportStream::PauseDMA
609 *****************************************************************************
610 * This routine pauses a hardware stream by reseting the Run/Pause bit in the
611 * control registers, leaving DMA registers content intact so that the stream
612 * can later be resumed.
613 */
615{
616 DOUT (DBG_PRINT, ("PauseDMA"));
617
619 UpdateDMA();
620}
621
622
624 WriteBMControlRegister (m_ulBDAddr + addr, data); }
626 WriteBMControlRegister (m_ulBDAddr + addr, data); }
628 WriteBMControlRegister (m_ulBDAddr + addr, data); }
630 ReadBMControlRegister8 (m_ulBDAddr + addr); }
632 ReadBMControlRegister16 (m_ulBDAddr + addr); }
634 ReadBMControlRegister32 (m_ulBDAddr + addr); }
#define PAGED_CODE()
unsigned char BOOLEAN
@ AC97REG_LFE_SAMPLERATE
Definition: ac97reg.h:44
@ AC97REG_MIC_SAMPLERATE
Definition: ac97reg.h:46
@ AC97REG_RECORD_SAMPLERATE
Definition: ac97reg.h:45
@ AC97REG_SURROUND_SAMPLERATE
Definition: ac97reg.h:43
@ AC97REG_FRONT_SAMPLERATE
Definition: ac97reg.h:42
static int state
Definition: maze.c:121
LONG NTSTATUS
Definition: precomp.h:26
#define STDMETHODIMP_(t)
Definition: basetyps.h:44
const GUID IID_IUnknown
struct _Capture Capture
Definition: capture.h:24
tBDEntry * BDList
Definition: stream.h:61
NTSTATUS NonDelegatingQueryInterface(_In_ REFIID Interface, _COM_Outptr_ PVOID *Object, _In_ REFIID iStream, _In_ PUNKNOWN stream)
Definition: stream.cpp:19
void WriteReg16(ULONG addr, USHORT data)
Definition: stream.cpp:625
WORD NumberOfChannels
Definition: stream.h:45
void ResumeDMA(ULONG state=DMA_ENGINE_ON)
Definition: stream.cpp:599
UCHAR ReadReg8(ULONG addr)
Definition: stream.cpp:629
ULONG CurrentRate
Definition: stream.h:56
void WriteReg32(ULONG addr, ULONG data)
Definition: stream.cpp:627
CMiniport * Miniport
Definition: stream.h:42
void ResetDMA(void)
Definition: stream.cpp:544
virtual NTSTATUS Init_() PURE
NTSTATUS Init(IN CMiniport *Miniport_, IN PUNKNOWN PortStream, IN WavePins Pin_, IN BOOLEAN Capture_, IN PKSDATAFORMAT DataFormat_, OUT PSERVICEGROUP *ServiceGroup_)
Definition: stream.cpp:65
virtual void PowerChangeNotify_(IN POWER_STATE NewState)
Definition: stream2.cpp:51
int GetBuffPos(DWORD *buffPos)
Definition: stream.cpp:509
USHORT ReadReg16(ULONG addr)
Definition: stream.cpp:631
void PowerChangeNotify(IN POWER_STATE NewState)
Definition: stream.cpp:289
void PauseDMA(void)
Definition: stream.cpp:614
PPORTSTREAM_ PortStream
Definition: stream.h:48
PSERVICEGROUP ServiceGroup
Definition: stream.h:55
UCHAR UpdateDMA(void)
Definition: stream.cpp:493
void WriteReg8(ULONG addr, UCHAR data)
Definition: stream.cpp:623
ULONG DMAEngineState
Definition: stream.h:57
ULONG ReadReg32(ULONG addr)
Definition: stream.cpp:633
WavePins Pin
Definition: stream.h:43
PHYSICAL_ADDRESS BDList_PhysAddr
Definition: stream.h:60
DEVICE_POWER_STATE m_PowerState
Definition: stream.h:46
ULONG m_ulBDAddr
Definition: stream.h:58
CMiniportStream * Streams[PIN_MICIN_OFFSET+1]
Definition: miniport.h:29
PADAPTERCOMMON AdapterCommon
Definition: miniport.h:31
NTSTATUS TestDataFormat(IN PKSDATAFORMAT Format, IN WavePins Pin)
Definition: miniport.cpp:827
IUnknown * PUNKNOWN
Definition: com_apitest.h:45
#define NULL
Definition: types.h:112
#define TRUE
Definition: types.h:120
#define FALSE
Definition: types.h:117
#define NT_SUCCESS(StatCode)
Definition: apphelp.c:33
void __fastcall obj_AddRef(PUNKNOWN obj, void **ppvObject)
Definition: miniport.cpp:1264
void __fastcall obj_Release(void **ppvObject)
Definition: miniport.cpp:1273
const int DBG_POWER
Definition: debug.h:23
#define DOUT(lvl, strings)
Definition: debug.h:82
const int PIN_WAVEIN_OFFSET
Definition: miniport.h:22
const int PIN_WAVEOUT_OFFSET
Definition: miniport.h:21
WavePins
Definition: shared.h:317
@ PIN_WAVEOUT
Definition: shared.h:318
@ PIN_WAVEIN
Definition: shared.h:320
@ NODEC_SURROUND_DAC_PRESENT
Definition: shared.h:101
@ NODEC_LFE_DAC_PRESENT
Definition: shared.h:102
@ NODEC_PCM_VSR_INDEPENDENT_RATES
Definition: shared.h:97
const int DMA_ENGINE_PAUSE
Definition: stream.h:5
const int DMA_ENGINE_OFF
Definition: stream.h:4
const int DMA_ENGINE_ON
Definition: stream.h:7
IDrmAudioStream * PDRMAUDIOSTREAM
Definition: drmk.h:109
unsigned int BOOL
Definition: ntddk_ex.h:94
unsigned long DWORD
Definition: ntddk_ex.h:95
GLuint GLuint GLsizei count
Definition: gl.h:1545
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: gl.h:1950
GLenum const GLvoid * addr
Definition: glext.h:9621
const ULONG X_CR
Definition: ichreg.h:56
const ULONG X_PICB
Definition: ichreg.h:54
const UCHAR CR_RPBM
Definition: ichreg.h:99
const ULONG GLOB_CNT
Definition: ichreg.h:42
const UCHAR CR_IOCE
Definition: ichreg.h:96
const ULONG GLOB_CNT_PCM4
Definition: ichreg.h:66
const UCHAR CR_RR
Definition: ichreg.h:100
const ULONG X_CIV
Definition: ichreg.h:51
const ULONG PI_BDBAR
Definition: ichreg.h:21
const UCHAR CR_LVBIE
Definition: ichreg.h:98
const ULONG PO_BDBAR
Definition: ichreg.h:28
const ULONG MC_BDBAR
Definition: ichreg.h:35
struct KSDATAFORMAT_WAVEFORMATEX * PKSDATAFORMAT_WAVEFORMATEX
WAVEFORMATPCMEX * PWAVEFORMATPCMEX
Definition: mmreg.h:468
#define ASSERT(a)
Definition: mode.c:44
#define KeStallExecutionProcessor(MicroSeconds)
Definition: precomp.h:27
#define DBG_ERROR
Definition: nfs41_debug.h:78
#define _COM_Outptr_
Definition: no_sal2.h:278
#define _In_
Definition: no_sal2.h:158
#define UNREFERENCED_PARAMETER(P)
Definition: ntbasedef.h:325
@ PowerDeviceD1
Definition: ntpoapi.h:50
@ PowerDeviceD0
Definition: ntpoapi.h:49
@ PowerDeviceD2
Definition: ntpoapi.h:51
@ PowerDeviceD3
Definition: ntpoapi.h:52
unsigned short USHORT
Definition: pedump.c:61
IServiceGroup * PSERVICEGROUP
Definition: portcls.h:614
#define REFIID
Definition: guiddef.h:118
NTSTATUS NTAPI PcNewServiceGroup(OUT PSERVICEGROUP *OutServiceGroup, IN PUNKNOWN OuterUnknown OPTIONAL)
#define STATUS_SUCCESS
Definition: shellext.h:65
Definition: drmk.h:18
WAVEFORMATEX Format
Definition: ksmedia.h:638
DWORD nSamplesPerSec
Definition: audioclient.idl:42
Definition: parse.h:23
WORD wLength
Definition: stream.h:32
void * PVOID
Definition: typedefs.h:50
#define IN
Definition: typedefs.h:39
uint32_t ULONG
Definition: typedefs.h:59
#define OUT
Definition: typedefs.h:40
#define STATUS_INVALID_PARAMETER
Definition: udferr_usr.h:135
#define STATUS_UNSUCCESSFUL
Definition: udferr_usr.h:132
ULONG LowPart
Definition: typedefs.h:106
_Must_inspect_result_ _In_ WDFCOLLECTION _In_ WDFOBJECT Object
_Must_inspect_result_ _In_ WDFDEVICE _In_ LPCGUID _Out_ PINTERFACE Interface
Definition: wdffdo.h:465
#define IsEqualGUIDAligned(guid1, guid2)
Definition: wdm.template.h:235
#define DBG_PRINT(ppi, ch, level)
Definition: win32kdebug.h:169
unsigned char UCHAR
Definition: xmlstorage.h:181