D3D11CdmProxy should be reinitializable after hardware content protection teardown
- Clean up after hardware content protection teardown notification so that
another call to Initialize() would succeed.
- Once hardware content protection teardown is notified to the client, it should
not notify more until it is reinitialized.
- Notify clients of hardware reset on power resume instead of power sleep.
Bug: 924794
Change-Id: Ia42768f86e012da59c07b3974cd0f1faf207f263
Reviewed-on: https://chromium-review.googlesource.com/c/1450648
Commit-Queue: Rintaro Kuroiwa <rkuroiwa@chromium.org>
Reviewed-by: Xiaohan Wang <xhwang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#629659}
diff --git a/media/gpu/windows/d3d11_cdm_proxy.cc b/media/gpu/windows/d3d11_cdm_proxy.cc
index b188fc5..930da80 100644
--- a/media/gpu/windows/d3d11_cdm_proxy.cc
+++ b/media/gpu/windows/d3d11_cdm_proxy.cc
@@ -163,7 +163,7 @@
// Return true on success.
bool RegisterHardwareContentProtectionTeardown(ComPtr<ID3D11Device> device);
- // Regiesters for power events, specifically power suspend event.
+ // Regiesters for power events, specifically power resume event.
// Returns true on success.
bool RegisterPowerEvents();
@@ -172,7 +172,7 @@
// base::PowerObserver implementation. Other power events are not relevant to
// this class.
- void OnSuspend() override;
+ void OnResume() override;
// Stops watching for events. Good for clean up.
void StopWatching();
@@ -543,6 +543,26 @@
void D3D11CdmProxy::NotifyHardwareContentProtectionTeardown() {
cdm_context_->OnHardwareReset();
client_->NotifyHardwareReset();
+ Reset();
+}
+
+void D3D11CdmProxy::Reset() {
+ client_ = nullptr;
+ initialized_ = false;
+ crypto_session_map_.clear();
+ device_.Reset();
+ device_context_.Reset();
+ video_device_.Reset();
+ video_device1_.Reset();
+ video_context_.Reset();
+ video_context1_.Reset();
+ // Note that this deregisters hardware reset event watcher. It shouldn't
+ // notify the clients until this is reinitialized. Also the client is set to
+ // null in this method.
+ hardware_event_watcher_ = nullptr;
+ crypto_session_map_.clear();
+ private_input_size_ = 0;
+ private_output_size_ = 0;
}
D3D11CdmProxy::HardwareEventWatcher::~HardwareEventWatcher() {
@@ -627,7 +647,7 @@
teardown_callback_.Run();
}
-void D3D11CdmProxy::HardwareEventWatcher::OnSuspend() {
+void D3D11CdmProxy::HardwareEventWatcher::OnResume() {
teardown_callback_.Run();
}
diff --git a/media/gpu/windows/d3d11_cdm_proxy.h b/media/gpu/windows/d3d11_cdm_proxy.h
index 1fb77103f..8b62d2f 100644
--- a/media/gpu/windows/d3d11_cdm_proxy.h
+++ b/media/gpu/windows/d3d11_cdm_proxy.h
@@ -75,6 +75,9 @@
void NotifyHardwareContentProtectionTeardown();
+ // Reset the state of this instance to be reinitializable.
+ void Reset();
+
const GUID crypto_type_;
const CdmProxy::Protocol protocol_;
const FunctionIdMap function_id_map_;
@@ -90,6 +93,8 @@
// Counter for assigning IDs to crypto sessions.
uint32_t next_crypto_session_id_ = 1;
+ // Everything from here until weak ptr factory (which must be at the end)
+ // should be reset in Reset().
Client* client_ = nullptr;
bool initialized_ = false;
diff --git a/media/gpu/windows/d3d11_cdm_proxy_unittest.cc b/media/gpu/windows/d3d11_cdm_proxy_unittest.cc
index ff24d6b..cee91d5 100644
--- a/media/gpu/windows/d3d11_cdm_proxy_unittest.cc
+++ b/media/gpu/windows/d3d11_cdm_proxy_unittest.cc
@@ -24,6 +24,7 @@
using ::testing::_;
using ::testing::AllOf;
using ::testing::AtLeast;
+using ::testing::AtMost;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
@@ -51,8 +52,13 @@
class MockPowerMonitorSource : public base::PowerMonitorSource {
public:
- // Use this method to send a power suspend event.
- void Suspend() { ProcessPowerEvent(SUSPEND_EVENT); }
+ // Use this method to send a power resume event.
+ void Resume() {
+ // Due to how ProcessPowerEvent() works, it has to be suspended first to
+ // resume.
+ ProcessPowerEvent(SUSPEND_EVENT);
+ ProcessPowerEvent(RESUME_EVENT);
+ }
MOCK_METHOD0(Shutdown, void());
MOCK_METHOD0(IsOnBatteryPowerImpl, bool());
@@ -92,7 +98,7 @@
function_id_map[kTestFunction] = kTestFunctionId;
// Use NiceMock because we don't care about base::PowerMonitorSource events
- // other than calling Suspend() directly.
+ // other than calling Resume() directly.
auto mock_power_monitor_source =
std::make_unique<NiceMock<MockPowerMonitorSource>>();
mock_power_monitor_source_ = mock_power_monitor_source.get();
@@ -189,8 +195,8 @@
Return(S_OK)));
}
- // Helper method to do Initialize(). Only useful if the test doesn't require
- // access to the mocks later.
+ // Helper method to do Initialize(). The returned mock objects are accessible
+ // thru member variables.
void Initialize(CdmProxy::Client* client, CdmProxy::InitializeCB callback) {
EXPECT_CALL(create_device_mock_,
Create(_, D3D_DRIVER_TYPE_HARDWARE, _, _, _, _, _, _, _, _));
@@ -235,10 +241,33 @@
Mock::VerifyAndClearExpectations(video_context1_mock_.Get());
}
+ // Test case where the proxy is initialized and then hardware content
+ // protection teardown is notified.
+ void HardwareContentProtectionTeardown() {
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(callback_mock_,
+ InitializeCallback(CdmProxy::Status::kOk, _, _));
+ ASSERT_NO_FATAL_FAILURE(Initialize(
+ &client_, base::BindOnce(&CallbackMock::InitializeCallback,
+ base::Unretained(&callback_mock_))));
+
+ EXPECT_CALL(client_, NotifyHardwareReset());
+
+ base::MockCallback<CdmContext::EventCB> event_cb;
+ auto callback_registration =
+ proxy_->GetCdmContext()->RegisterEventCB(event_cb.Get());
+ EXPECT_CALL(event_cb, Run(CdmContext::Event::kHardwareContextLost))
+ .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
+
+ SetEvent(teardown_event_);
+ run_loop.Run();
+ }
+
MockProxyClient client_;
std::unique_ptr<D3D11CdmProxy> proxy_;
std::unique_ptr<base::PowerMonitor> power_monitor_;
- // Owned by power_monitor_. Use this to simulate a power-suspend.
+ // Owned by power_monitor_. Use this to simulate a power-resume.
MockPowerMonitorSource* mock_power_monitor_source_;
D3D11CreateDeviceMock create_device_mock_;
@@ -291,22 +320,16 @@
// Hardware content protection teardown is notified to the proxy.
// Verify that the client is notified.
TEST_F(D3D11CdmProxyTest, HardwareContentProtectionTeardown) {
- base::RunLoop run_loop;
+ EXPECT_NO_FATAL_FAILURE(HardwareContentProtectionTeardown());
+}
- EXPECT_CALL(client_, NotifyHardwareReset());
-
- base::MockCallback<CdmContext::EventCB> event_cb;
- auto callback_registration =
- proxy_->GetCdmContext()->RegisterEventCB(event_cb.Get());
- EXPECT_CALL(event_cb, Run(CdmContext::Event::kHardwareContextLost))
- .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
-
+// Verify that initialization after hardware content protection teardown works..
+TEST_F(D3D11CdmProxyTest, HardwareContentProtectionTeardownThenInitialize) {
+ ASSERT_NO_FATAL_FAILURE(HardwareContentProtectionTeardown());
EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
- SetEvent(teardown_event_);
- run_loop.Run();
}
// Verify that failing to register to hardware content protection teardown
@@ -326,18 +349,41 @@
}
// Verify that the client is notified on power suspend.
-TEST_F(D3D11CdmProxyTest, PowerSuspend) {
+TEST_F(D3D11CdmProxyTest, PowerResume) {
base::RunLoop run_loop;
- EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
- run_loop.Quit();
- }));
-
EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
ASSERT_NO_FATAL_FAILURE(
Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
base::Unretained(&callback_mock_))));
- mock_power_monitor_source_->Suspend();
+
+ EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
+ run_loop.Quit();
+ }));
+
+ mock_power_monitor_source_->Resume();
+ run_loop.Run();
+}
+
+// IRL power resume is notified and then hardware content protection teardown
+// is notified. Make sure that the two notifications don't signal the clients
+// more than once (without being reinitialized in between the notifications).
+// Note that this test uses QuitWhenIdle(). If both notifications are processed
+// this test will run forever.
+TEST_F(D3D11CdmProxyTest, PowerResumeAndHardwareContentProtectionTeardown) {
+ base::RunLoop run_loop;
+
+ EXPECT_CALL(callback_mock_, InitializeCallback(CdmProxy::Status::kOk, _, _));
+ ASSERT_NO_FATAL_FAILURE(
+ Initialize(&client_, base::BindOnce(&CallbackMock::InitializeCallback,
+ base::Unretained(&callback_mock_))));
+
+ EXPECT_CALL(client_, NotifyHardwareReset())
+ .Times(1)
+ .WillOnce(Invoke([&run_loop]() { run_loop.QuitWhenIdle(); }));
+
+ mock_power_monitor_source_->Resume();
+ SetEvent(teardown_event_);
run_loop.Run();
}
@@ -744,14 +790,6 @@
TEST_F(D3D11CdmProxyTest, ClearKeysAfterHardwareContentProtectionTeardown) {
base::RunLoop run_loop;
- EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
- run_loop.Quit();
- }));
-
- base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
- ASSERT_TRUE(context);
- CdmProxyContext* proxy_context = context->GetCdmProxyContext();
-
uint32_t crypto_session_id_from_initialize = 0;
EXPECT_CALL(callback_mock_,
InitializeCallback(CdmProxy::Status::kOk, kTestProtocol, _))
@@ -775,9 +813,17 @@
base::BindOnce(&CallbackMock::SetKeyCallback,
base::Unretained(&callback_mock_)));
+ EXPECT_CALL(client_, NotifyHardwareReset()).WillOnce(Invoke([&run_loop]() {
+ run_loop.Quit();
+ }));
+
SetEvent(teardown_event_);
run_loop.Run();
+ base::WeakPtr<CdmContext> context = proxy_->GetCdmContext();
+ ASSERT_TRUE(context);
+ CdmProxyContext* proxy_context = context->GetCdmProxyContext();
+
std::string key_id_str(kKeyId.begin(), kKeyId.end());
auto decrypt_context =
proxy_context->GetD3D11DecryptContext(kTestKeyType, key_id_str);