blob: 4194219e162b475f6e9fe876c9b21243f6ae2092 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <ddk/device.h>
#include <ddktl/device-internal.h>
#include <magenta/assert.h>
#include <fbl/type_support.h>
// ddk::Device<D, ...>
//
// Notes:
//
// ddk::Device<D, ...> is a mixin class that simplifies writing DDK drivers in
// C++. The DDK's mx_device_t defines a set of function pointer callbacks that
// can be implemented to define standard behavior (e.g., open/close/read/write),
// as well as to implement device lifecycle events (e.g., unbind/release). The
// mixin classes are used to set up the function pointer table to call methods
// from the user's class automatically.
//
// Every ddk::Device subclass must implement the following release callback to
// cleanup resources:
//
// void DdkRelease();
//
//
// :: Available mixins ::
// +----------------------+----------------------------------------------------+
// | Mixin class | Required function implementation |
// +----------------------+----------------------------------------------------+
// | ddk::GetProtocolable | mx_status_t DdkGetProtocol(uint32_t proto_id, |
// | | void* out) |
// | | |
// | ddk::Openable | mx_status_t DdkOpen(mx_device_t** dev_out, |
// | | uint32_t flags) |
// | | |
// | ddk::OpenAtable | mx_status_t DdkOpenAt(mx_device_t** dev_out, |
// | | const char* path, |
// | | uint32_t flags) |
// | | |
// | ddk::Closable | mx_status_t DdkClose(uint32_t flags) |
// | | |
// | ddk::Unbindable | void DdkUnbind() |
// | | |
// | ddk::Readable | mx_status_t DdkRead(void* buf, size_t count, |
// | | mx_off_t off, size_t* actual) |
// | | |
// | ddk::Writable | mx_status_t DdkWrite(const void* buf, |
// | | size_t count, mx_off_t off, |
// | | size_t* actual) |
// | | |
// | ddk::IotxnQueueable | void DdkIotxnQueue(iotxn_t* txn) |
// | | |
// | ddk::GetSizable | mx_off_t DdkGetSize() |
// | | |
// | ddk::Ioctlable | mx_status_t DdkIoctl(uint32_t op, |
// | | const void* in_buf, |
// | | size_t in_len, void* out_buf, |
// | | size_t out_len, |
// | | size_t* actual) |
// | | |
// | ddk::Suspendable | mx_status_t DdkSuspend(uint32_t flags) |
// | | |
// | ddk::Resumable | mx_status_t DdkResume(uint32_t flags) |
// +----------------------+----------------------------------------------------+
//
// Note: the ddk::FullDevice type alias may also be used if your device class
// will implement every mixin.
//
//
// :: Example ::
//
// // Define our device type using a type alias.
// class MyDevice;
// using DeviceType = ddk::Device<MyDevice, ddk::Openable, ddk::Closable,
// ddk::Readable, ddk::Unbindable>;
//
// class MyDevice : public DeviceType {
// public:
// MyDevice(mx_device_t* parent)
// : DeviceType(parent) {}
//
// mx_status_t Bind() {
// // Any other setup required by MyDevice. The device_add_args_t will be filled out by the
// // base class.
// return DdkAdd("my-device-name");
// }
//
// // Methods required by the ddk mixins
// mx_status_t DdkOpen(mx_device_t** dev_out, uint32_t flags);
// mx_status_t DdkClose(uint32_t flags);
// mx_status_t DdkRead(void* buf, size_t count, mx_off_t off, size_t* actual);
// void DdkUnbind();
// void DdkRelease();
// };
//
// extern "C" mx_status_t my_bind(mx_device_t* device,
// void** cookie) {
// auto dev = unique_ptr<MyDevice>(new MyDevice(device));
// auto status = dev->Bind();
// if (status == MX_OK) {
// // devmgr is now in charge of the memory for dev
// dev.release();
// }
// return status;
// }
//
// See also: protocol mixins for setting protocol_id and protocol_ops.
namespace ddk {
struct AnyProtocol {
void* ops;
void* ctx;
};
// DDK Device mixins
template <typename D>
class GetProtocolable : public internal::base_mixin {
protected:
explicit GetProtocolable(mx_protocol_device_t* proto) {
internal::CheckGetProtocolable<D>();
proto->get_protocol = GetProtocol;
}
private:
static mx_status_t GetProtocol(void* ctx, uint32_t proto_id, void* out) {
return static_cast<D*>(ctx)->DdkGetProtocol(proto_id, out);
}
};
template <typename D>
class Openable : public internal::base_mixin {
protected:
explicit Openable(mx_protocol_device_t* proto) {
internal::CheckOpenable<D>();
proto->open = Open;
}
private:
static mx_status_t Open(void* ctx, mx_device_t** dev_out, uint32_t flags) {
return static_cast<D*>(ctx)->DdkOpen(dev_out, flags);
}
};
template <typename D>
class OpenAtable : public internal::base_mixin {
protected:
explicit OpenAtable(mx_protocol_device_t* proto) {
internal::CheckOpenAtable<D>();
proto->open_at = OpenAt;
}
private:
static mx_status_t OpenAt(void* ctx, mx_device_t** dev_out, const char* path,
uint32_t flags) {
return static_cast<D*>(ctx)->DdkOpenAt(dev_out, path, flags);
}
};
template <typename D>
class Closable : public internal::base_mixin {
protected:
explicit Closable(mx_protocol_device_t* proto) {
internal::CheckClosable<D>();
proto->close = Close;
}
private:
static mx_status_t Close(void* ctx, uint32_t flags) {
return static_cast<D*>(ctx)->DdkClose(flags);
}
};
template <typename D>
class Unbindable : public internal::base_mixin {
protected:
explicit Unbindable(mx_protocol_device_t* proto) {
internal::CheckUnbindable<D>();
proto->unbind = Unbind;
}
private:
static void Unbind(void* ctx) {
static_cast<D*>(ctx)->DdkUnbind();
}
};
template <typename D>
class Readable : public internal::base_mixin {
protected:
explicit Readable(mx_protocol_device_t* proto) {
internal::CheckReadable<D>();
proto->read = Read;
}
private:
static mx_status_t Read(void* ctx, void* buf, size_t count, mx_off_t off, size_t* actual) {
return static_cast<D*>(ctx)->DdkRead(buf, count, off, actual);
}
};
template <typename D>
class Writable : public internal::base_mixin {
protected:
explicit Writable(mx_protocol_device_t* proto) {
internal::CheckWritable<D>();
proto->write = Write;
}
private:
static mx_status_t Write(void* ctx, const void* buf, size_t count, mx_off_t off,
size_t* actual) {
return static_cast<D*>(ctx)->DdkWrite(buf, count, off, actual);
}
};
template <typename D>
class IotxnQueueable : public internal::base_mixin {
protected:
explicit IotxnQueueable(mx_protocol_device_t* proto) {
internal::CheckIotxnQueueable<D>();
proto->iotxn_queue = IotxnQueue;
}
private:
static void IotxnQueue(void* ctx, iotxn_t* txn) {
static_cast<D*>(ctx)->DdkIotxnQueue(txn);
}
};
template <typename D>
class GetSizable : public internal::base_mixin {
protected:
explicit GetSizable(mx_protocol_device_t* proto) {
internal::CheckGetSizable<D>();
proto->get_size = GetSize;
}
private:
static mx_off_t GetSize(void* ctx) {
return static_cast<D*>(ctx)->DdkGetSize();
}
};
template <typename D>
class Ioctlable : public internal::base_mixin {
protected:
explicit Ioctlable(mx_protocol_device_t* proto) {
internal::CheckIoctlable<D>();
proto->ioctl = Ioctl;
}
private:
static mx_status_t Ioctl(void* ctx, uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* out_actual) {
return static_cast<D*>(ctx)->DdkIoctl(op, in_buf, in_len, out_buf, out_len, out_actual);
}
};
template <typename D>
class Suspendable : public internal::base_mixin {
protected:
explicit Suspendable(mx_protocol_device_t* proto) {
internal::CheckSuspendable<D>();
proto->suspend = Suspend;
}
private:
static mx_status_t Suspend(void* ctx, uint32_t flags) {
return static_cast<D*>(ctx)->DdkSuspend(flags);
}
};
template <typename D>
class Resumable : public internal::base_mixin {
protected:
explicit Resumable(mx_protocol_device_t* proto) {
internal::CheckResumable<D>();
proto->resume = Resume;
}
private:
static mx_status_t Resume(void* ctx, uint32_t flags) {
return static_cast<D*>(ctx)->DdkResume(flags);
}
};
// Device is templated on the list of mixins that define which DDK device
// methods are implemented. Note that internal::base_device *must* be the
// left-most base class in order to ensure that its constructor runs before the
// mixin constructors. This ensures that ddk_device_proto_ is zero-initialized
// before setting the fields in the mixins.
template <class D, template <typename> class... Mixins>
class Device : public ::ddk::internal::base_device, public Mixins<D>... {
public:
mx_status_t DdkAdd(const char* name) {
if (mxdev_ != nullptr) {
return MX_ERR_BAD_STATE;
}
device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = name;
// Since we are stashing this as a D*, we can use ctx in all
// the callback functions and cast it directly to a D*.
args.ctx = static_cast<D*>(this);
args.ops = &ddk_device_proto_;
AddProtocol(&args);
return device_add(parent_, &args, &mxdev_);
}
mx_status_t DdkRemove() {
if (mxdev_ == nullptr) {
return MX_ERR_BAD_STATE;
}
mx_status_t res = device_remove(mxdev_);
mxdev_ = nullptr;
return res;
}
const char* name() const { return mxdev() ? device_get_name(mxdev()) : nullptr; }
// The opaque pointer representing this device.
mx_device_t* mxdev() const { return mxdev_; }
// The opaque pointer representing the device's parent.
mx_device_t* parent() const { return parent_; }
void SetState(mx_signals_t stateflag) {
device_state_set(mxdev_, stateflag);
}
void ClearState(mx_signals_t stateflag) {
device_state_clr(mxdev_, stateflag);
}
void ClearAndSetState(mx_signals_t clearflag, mx_signals_t setflag) {
device_state_clr_set(mxdev_, clearflag, setflag);
}
protected:
Device(mx_device_t* parent)
: internal::base_device(parent),
Mixins<D>(&ddk_device_proto_)... {
internal::CheckMixins<Mixins<D>...>();
internal::CheckReleasable<D>();
ddk_device_proto_.release = DdkReleaseThunk;
}
private:
static void DdkReleaseThunk(void* ctx) {
static_cast<D*>(ctx)->DdkRelease();
}
template <typename T>
using is_protocol = fbl::is_base_of<internal::base_protocol, T>;
// Add the protocol id and ops if D inherits from a base_protocol implementation.
template <typename T = D>
void AddProtocol(device_add_args_t* args,
typename fbl::enable_if<is_protocol<T>::value, T>::type* dummy = 0) {
auto dev = static_cast<D*>(this);
MX_ASSERT(dev->ddk_proto_id_ > 0);
args->proto_id = dev->ddk_proto_id_;
args->proto_ops = dev->ddk_proto_ops_;
}
// If D does not inherit from a base_protocol implementation, do nothing.
template <typename T = D>
void AddProtocol(device_add_args_t* args,
typename fbl::enable_if<!is_protocol<T>::value, T>::type* dummy = 0) {}
};
// Convenience type for implementations that would like to override all
// mx_protocol_device_t methods.
template <class D>
using FullDevice = Device<D,
GetProtocolable,
Openable,
OpenAtable,
Closable,
Unbindable,
Readable,
Writable,
IotxnQueueable,
GetSizable,
Ioctlable,
Suspendable,
Resumable>;
} // namespace ddk