mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 05:49:27 +00:00
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)
Bug: 166295507 Merged-In: I6502829205ede2de914b27e6c2c5c42916af2b39 Change-Id: I7cb06511e43bd1fffd5f80a11dbdf5b1314cfe8e
This commit is contained in:
14
Android.bp
14
Android.bp
@@ -21,7 +21,7 @@ cc_defaults {
|
||||
|
||||
// Lists all dependencies that can *not* be expected on the device.
|
||||
static_libs: [
|
||||
"VtsHalHidlTargetTestBase",
|
||||
"VtsHalHidlTestUtils",
|
||||
"libhidl-gen-utils",
|
||||
],
|
||||
|
||||
@@ -47,3 +47,15 @@ cc_defaults {
|
||||
|
||||
require_root: true,
|
||||
}
|
||||
|
||||
// TODO: Remove this after all vts tests under vendor/qcom are converted to
|
||||
// parameterized gtest.
|
||||
cc_defaults {
|
||||
name: "Vts10HalTargetTestDefaults",
|
||||
defaults: [
|
||||
"VtsHalTargetTestDefaults",
|
||||
],
|
||||
static_libs: [
|
||||
"VtsHalHidlTargetTestBase",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -84,3 +84,4 @@ $(call add-clean-step, rm -f $(PRODUCT_OUT)/etc/init/android.hardware.audio@2.0-
|
||||
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.cas@1.1*)
|
||||
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.cas@1.1*)
|
||||
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/android.hardware.cas@1.1*)
|
||||
$(call add-clean-step, rm -rf $(OUT_DIR)/soong/.intermediates/hardware/interfaces/wifi/1.4/android.hardware.wifi@1.4-adapter_genc++/)
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
},
|
||||
{
|
||||
"name": "hal_implementation_test"
|
||||
},
|
||||
{
|
||||
"name": "VtsHalTvInputV1_0TargetTest"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ hidl_interface {
|
||||
"IStreamIn.hal",
|
||||
"IStreamOut.hal",
|
||||
"IStreamOutCallback.hal",
|
||||
"IStreamOutEventCallback.hal",
|
||||
],
|
||||
interfaces: [
|
||||
"android.hardware.audio.common@6.0",
|
||||
|
||||
@@ -149,7 +149,10 @@ interface IDevice {
|
||||
AudioConfig suggestedConfig);
|
||||
|
||||
/**
|
||||
* Returns whether HAL supports audio patches.
|
||||
* Returns whether HAL supports audio patches. Patch represents a connection
|
||||
* between signal source(s) and signal sink(s). If HAL doesn't support
|
||||
* patches natively (in hardware) then audio system will need to establish
|
||||
* them in software.
|
||||
*
|
||||
* @return supports true if audio patches are supported.
|
||||
*/
|
||||
@@ -167,6 +170,25 @@ interface IDevice {
|
||||
createAudioPatch(vec<AudioPortConfig> sources, vec<AudioPortConfig> sinks)
|
||||
generates (Result retval, AudioPatchHandle patch);
|
||||
|
||||
/**
|
||||
* Updates an audio patch.
|
||||
*
|
||||
* Use of this function is preferred to releasing and re-creating a patch
|
||||
* as the HAL module can figure out a way of switching the route without
|
||||
* causing audio disruption.
|
||||
*
|
||||
* @param previousPatch handle of the previous patch to update.
|
||||
* @param sources new patch sources.
|
||||
* @param sinks new patch sinks.
|
||||
* @return retval operation completion status.
|
||||
* @return patch updated patch handle.
|
||||
*/
|
||||
updateAudioPatch(
|
||||
AudioPatchHandle previousPatch,
|
||||
vec<AudioPortConfig> sources,
|
||||
vec<AudioPortConfig> sinks) generates (
|
||||
Result retval, AudioPatchHandle patch);
|
||||
|
||||
/**
|
||||
* Release an audio patch.
|
||||
*
|
||||
@@ -297,7 +319,9 @@ interface IDevice {
|
||||
close() generates (Result retval);
|
||||
|
||||
/**
|
||||
* Applies an audio effect to an audio device.
|
||||
* Applies an audio effect to an audio device. The effect is inserted
|
||||
* according to its insertion preference specified by INSERT_... EffectFlags
|
||||
* in the EffectDescriptor.
|
||||
*
|
||||
* @param device identifies the sink or source device this effect must be applied to.
|
||||
* "device" is the AudioPortHandle indicated for the device when the audio
|
||||
|
||||
@@ -274,6 +274,7 @@ interface IStream {
|
||||
*
|
||||
* @param minSizeFrames minimum buffer size requested. The actual buffer
|
||||
* size returned in struct MmapBufferInfo can be larger.
|
||||
* The size must be a positive value.
|
||||
* @return retval OK in case the success.
|
||||
* NOT_SUPPORTED on non mmap mode streams
|
||||
* NOT_INITIALIZED in case of memory allocation error
|
||||
|
||||
@@ -19,6 +19,7 @@ package android.hardware.audio@6.0;
|
||||
import android.hardware.audio.common@6.0;
|
||||
import IStream;
|
||||
import IStreamOutCallback;
|
||||
import IStreamOutEventCallback;
|
||||
|
||||
interface IStreamOut extends IStream {
|
||||
/**
|
||||
@@ -167,6 +168,18 @@ interface IStreamOut extends IStream {
|
||||
*/
|
||||
clearCallback() generates (Result retval);
|
||||
|
||||
/**
|
||||
* Set the callback interface for notifying about an output stream event.
|
||||
*
|
||||
* Calling this method with a null pointer will result in releasing
|
||||
* the local callback proxy on the server side and thus dereference
|
||||
* the callback implementation on the client side.
|
||||
*
|
||||
* @return retval operation completion status.
|
||||
*/
|
||||
setEventCallback(IStreamOutEventCallback callback)
|
||||
generates (Result retval);
|
||||
|
||||
/**
|
||||
* Returns whether HAL supports pausing and resuming of streams.
|
||||
*
|
||||
@@ -276,4 +289,90 @@ interface IStreamOut extends IStream {
|
||||
*/
|
||||
selectPresentation(int32_t presentationId, int32_t programId)
|
||||
generates (Result retval);
|
||||
|
||||
/**
|
||||
* Returns the Dual Mono mode presentation setting.
|
||||
*
|
||||
* Optional method
|
||||
*
|
||||
* @return retval operation completion status.
|
||||
* @return mode current setting of Dual Mono mode.
|
||||
*/
|
||||
getDualMonoMode() generates (Result retval, DualMonoMode mode);
|
||||
|
||||
/**
|
||||
* Sets the Dual Mono mode presentation on the output device.
|
||||
*
|
||||
* The Dual Mono mode is generally applied to stereo audio streams
|
||||
* where the left and right channels come from separate sources.
|
||||
*
|
||||
* Optional method
|
||||
*
|
||||
* @param mode selected Dual Mono mode.
|
||||
* @return retval operation completion status.
|
||||
*/
|
||||
setDualMonoMode(DualMonoMode mode) generates (Result retval);
|
||||
|
||||
/**
|
||||
* Returns the Audio Description Mix level in dB.
|
||||
*
|
||||
* The level is applied to streams incorporating a secondary Audio
|
||||
* Description stream. It specifies the relative level of mixing for
|
||||
* the Audio Description with a reference to the Main Audio.
|
||||
*
|
||||
* Optional method
|
||||
*
|
||||
* The value of the relative level is in the range from negative infinity
|
||||
* to +48.
|
||||
*
|
||||
* @return retval operation completion status.
|
||||
* @return leveldB the current Audio Description Mix Level in dB.
|
||||
*/
|
||||
getAudioDescriptionMixLevel() generates (Result retval, float leveldB);
|
||||
|
||||
/**
|
||||
* Sets the Audio Description Mix level in dB.
|
||||
*
|
||||
* For streams incorporating a secondary Audio Description stream
|
||||
* the relative level of mixing of the Audio Description to the Main Audio
|
||||
* is controlled by this method.
|
||||
*
|
||||
* Optional method
|
||||
*
|
||||
* The value of the relative level must be in the range from negative
|
||||
* infinity to +48.
|
||||
*
|
||||
* @param leveldB Audio Description Mix Level in dB
|
||||
* @return retval operation completion status.
|
||||
*/
|
||||
setAudioDescriptionMixLevel(float leveldB) generates (Result retval);
|
||||
|
||||
/**
|
||||
* Retrieves current playback rate parameters.
|
||||
*
|
||||
* Optional method
|
||||
*
|
||||
* @return retval operation completion status.
|
||||
* @return playbackRate current playback parameters
|
||||
*/
|
||||
getPlaybackRateParameters()
|
||||
generates (Result retval, PlaybackRate playbackRate);
|
||||
|
||||
/**
|
||||
* Sets the playback rate parameters that control playback behavior.
|
||||
* This is normally used when playing encoded content and decoding
|
||||
* is performed in hardware. Otherwise, the framework can apply
|
||||
* necessary transformations.
|
||||
*
|
||||
* Optional method
|
||||
*
|
||||
* If the HAL supports setting the playback rate, it is recommended
|
||||
* to support speed and pitch values at least in the range
|
||||
* from 0.5f to 2.0f, inclusive (see the definition of PlaybackRate struct).
|
||||
*
|
||||
* @param playbackRate playback parameters
|
||||
* @return retval operation completion status.
|
||||
*/
|
||||
setPlaybackRateParameters(PlaybackRate playbackRate)
|
||||
generates (Result retval);
|
||||
};
|
||||
|
||||
140
audio/6.0/IStreamOutEventCallback.hal
Normal file
140
audio/6.0/IStreamOutEventCallback.hal
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.audio@6.0;
|
||||
|
||||
/**
|
||||
* Asynchronous stream out event callback interface. The interface provides
|
||||
* a way for the HAL to notify platform when there are changes, e.g. codec
|
||||
* format change, from the lower layer.
|
||||
*/
|
||||
interface IStreamOutEventCallback {
|
||||
/**
|
||||
* Codec format changed.
|
||||
*
|
||||
* onCodecFormatChanged returns an AudioMetadata object in read-only ByteString format.
|
||||
* It represents the most recent codec format decoded by a HW audio decoder.
|
||||
*
|
||||
* Codec format is an optional message from HW audio decoders. It serves to
|
||||
* notify the application about the codec format and audio objects contained
|
||||
* within the compressed audio stream for control, informational,
|
||||
* and display purposes.
|
||||
*
|
||||
* audioMetadata ByteString is convertible to an AudioMetadata object through
|
||||
* both a C++ and a C API present in Metadata.h [1], or through a Java API present
|
||||
* in AudioMetadata.java [2].
|
||||
*
|
||||
* The ByteString format is a stable format used for parcelling (marshalling) across
|
||||
* JNI, AIDL, and HIDL interfaces. The test for R compatibility for native marshalling
|
||||
* is TEST(metadata_tests, compatibility_R) [3]. The test for R compatibility for JNI
|
||||
* marshalling is android.media.cts.AudioMetadataTest#testCompatibilityR [4].
|
||||
*
|
||||
* R (audio HAL 6.0) defined keys are as follows [2]:
|
||||
* "bitrate", int32
|
||||
* "channel-mask", int32
|
||||
* "mime", string
|
||||
* "sample-rate", int32
|
||||
* "bit-width", int32
|
||||
* "has-atmos", int32
|
||||
* "audio-encoding", int32
|
||||
*
|
||||
* Parceling Format:
|
||||
* All values are native endian order. [1]
|
||||
*
|
||||
* using type_size_t = uint32_t;
|
||||
* using index_size_t = uint32_t;
|
||||
* using datum_size_t = uint32_t;
|
||||
*
|
||||
* Permitted type indexes are
|
||||
* TYPE_NONE = 0, // Reserved
|
||||
* TYPE_INT32 = 1,
|
||||
* TYPE_INT64 = 2,
|
||||
* TYPE_FLOAT = 3,
|
||||
* TYPE_DOUBLE = 4,
|
||||
* TYPE_STRING = 5,
|
||||
* TYPE_DATA = 6, // A data table of <String, Datum>
|
||||
*
|
||||
* Datum = {
|
||||
* (type_size_t) Type (the type index from type_as_value<T>.)
|
||||
* (datum_size_t) Size (size of the Payload)
|
||||
* (byte string) Payload<Type>
|
||||
* }
|
||||
*
|
||||
* The data is specified in native endian order.
|
||||
* Since the size of the Payload is always present, unknown types may be skipped.
|
||||
*
|
||||
* Payload<Fixed-size Primitive_Value>
|
||||
* [ sizeof(Primitive_Value) in raw bytes ]
|
||||
*
|
||||
* Example of Payload<Int32> of 123:
|
||||
* Payload<Int32>
|
||||
* [ value of 123 ] = 0x7b 0x00 0x00 0x00 123
|
||||
*
|
||||
* Payload<String>
|
||||
* [ (index_size_t) length, not including zero terminator.]
|
||||
* [ (length) raw bytes ]
|
||||
*
|
||||
* Example of Payload<String> of std::string("hi"):
|
||||
* [ (index_size_t) length ] = 0x02 0x00 0x00 0x00 2 strlen("hi")
|
||||
* [ raw bytes "hi" ] = 0x68 0x69 "hi"
|
||||
*
|
||||
* Payload<Data>
|
||||
* [ (index_size_t) entries ]
|
||||
* [ raw bytes (entry 1) Key (Payload<String>)
|
||||
* Value (Datum)
|
||||
* ... (until #entries) ]
|
||||
*
|
||||
* Example of Payload<Data> of {{"hello", "world"},
|
||||
* {"value", (int32_t)1000}};
|
||||
* [ (index_size_t) #entries ] = 0x02 0x00 0x00 0x00 2 entries
|
||||
* Key (Payload<String>)
|
||||
* [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("hello")
|
||||
* [ raw bytes "hello" ] = 0x68 0x65 0x6c 0x6c 0x6f "hello"
|
||||
* Value (Datum)
|
||||
* [ (type_size_t) type ] = 0x05 0x00 0x00 0x00 5 (TYPE_STRING)
|
||||
* [ (datum_size_t) size ] = 0x09 0x00 0x00 0x00 sizeof(index_size_t) +
|
||||
* strlen("world")
|
||||
* Payload<String>
|
||||
* [ (index_size_t) length ] = 0x05 0x00 0x00 0x00 5 strlen("world")
|
||||
* [ raw bytes "world" ] = 0x77 0x6f 0x72 0x6c 0x64 "world"
|
||||
* Key (Payload<String>)
|
||||
* [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("value")
|
||||
* [ raw bytes "value" ] = 0x76 0x61 0x6c 0x75 0x65 "value"
|
||||
* Value (Datum)
|
||||
* [ (type_size_t) type ] = 0x01 0x00 0x00 0x00 1 (TYPE_INT32)
|
||||
* [ (datum_size_t) size ] = 0x04 0x00 0x00 0x00 4 sizeof(int32_t)
|
||||
* Payload<Int32>
|
||||
* [ raw bytes 1000 ] = 0xe8 0x03 0x00 0x00 1000
|
||||
*
|
||||
* The contents of audioMetadata is a Payload<Data>.
|
||||
* An implementation dependent detail is that the Keys are always
|
||||
* stored sorted, so the byte string representation generated is unique.
|
||||
*
|
||||
* Vendor keys are allowed for informational and debugging purposes.
|
||||
* Vendor keys should consist of the vendor company name followed
|
||||
* by a dot; for example, "vendorCompany.someVolume" [2].
|
||||
*
|
||||
* [1] system/media/audio_utils/include/audio_utils/Metadata.h
|
||||
* [2] frameworks/base/media/java/android/media/AudioMetadata.java
|
||||
* [3] system/media/audio_utils/tests/metadata_tests.cpp
|
||||
* [4] cts/tests/tests/media/src/android/media/cts/AudioMetadataTest.java
|
||||
*
|
||||
* @param audioMetadata is a buffer containing decoded format changes
|
||||
* reported by codec. The buffer contains data that can be transformed
|
||||
* to audio metadata, which is a C++ object based map.
|
||||
*/
|
||||
oneway onCodecFormatChanged(vec<uint8_t> audioMetadata);
|
||||
};
|
||||
@@ -258,8 +258,10 @@ package audio.policy.configuration.V6_0 {
|
||||
|
||||
public class GlobalConfiguration {
|
||||
ctor public GlobalConfiguration();
|
||||
method public boolean getCall_screen_mode_supported();
|
||||
method public audio.policy.configuration.V6_0.EngineSuffix getEngine_library();
|
||||
method public boolean getSpeaker_drc_enabled();
|
||||
method public void setCall_screen_mode_supported(boolean);
|
||||
method public void setEngine_library(audio.policy.configuration.V6_0.EngineSuffix);
|
||||
method public void setSpeaker_drc_enabled(boolean);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
</xs:element>
|
||||
<xs:complexType name="globalConfiguration">
|
||||
<xs:attribute name="speaker_drc_enabled" type="xs:boolean" use="required"/>
|
||||
<xs:attribute name="call_screen_mode_supported" type="xs:boolean" use="optional"/>
|
||||
<xs:attribute name="engine_library" type="engineSuffix" use="optional"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="modules">
|
||||
|
||||
@@ -247,3 +247,111 @@ enum MicrophoneDirection : int32_t {
|
||||
*/
|
||||
EXTERNAL = 3,
|
||||
};
|
||||
|
||||
|
||||
/* Dual Mono handling is used when a stereo audio stream
|
||||
* contains separate audio content on the left and right channels.
|
||||
* Such information about the content of the stream may be found, for example,
|
||||
* in ITU T-REC-J.94-201610 A.6.2.3 Component descriptor.
|
||||
*/
|
||||
@export(name="audio_dual_mono_mode_t", value_prefix="AUDIO_DUAL_MONO_MODE_")
|
||||
enum DualMonoMode : int32_t {
|
||||
// Need to be in sync with DUAL_MONO_MODE* constants in
|
||||
// frameworks/base/media/java/android/media/AudioTrack.java
|
||||
/**
|
||||
* Disable any Dual Mono presentation effect.
|
||||
*
|
||||
*/
|
||||
OFF = 0,
|
||||
/**
|
||||
* This mode indicates that a stereo stream should be presented
|
||||
* with the left and right audio channels blended together
|
||||
* and delivered to both channels.
|
||||
*
|
||||
* Behavior for non-stereo streams is implementation defined.
|
||||
* A suggested guideline is that the left-right stereo symmetric
|
||||
* channels are pairwise blended, the other channels such as center
|
||||
* are left alone.
|
||||
*/
|
||||
LR = 1,
|
||||
/**
|
||||
* This mode indicates that a stereo stream should be presented
|
||||
* with the left audio channel replicated into the right audio channel.
|
||||
*
|
||||
* Behavior for non-stereo streams is implementation defined.
|
||||
* A suggested guideline is that all channels with left-right
|
||||
* stereo symmetry will have the left channel position replicated
|
||||
* into the right channel position. The center channels (with no
|
||||
* left/right symmetry) or unbalanced channels are left alone.
|
||||
*/
|
||||
LL = 2,
|
||||
/**
|
||||
* This mode indicates that a stereo stream should be presented
|
||||
* with the right audio channel replicated into the left audio channel.
|
||||
*
|
||||
* Behavior for non-stereo streams is implementation defined.
|
||||
* A suggested guideline is that all channels with left-right
|
||||
* stereo symmetry will have the right channel position replicated
|
||||
* into the left channel position. The center channels (with no
|
||||
* left/right symmetry) or unbalanced channels are left alone.
|
||||
*/
|
||||
RR = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Algorithms used for timestretching (preserving pitch while playing audio
|
||||
* content at different speed).
|
||||
*/
|
||||
@export(name="audio_timestretch_stretch_mode_t", value_prefix="AUDIO_TIMESTRETCH_STRETCH_")
|
||||
enum TimestretchMode : int32_t {
|
||||
// Need to be in sync with AUDIO_STRETCH_MODE_* constants in
|
||||
// frameworks/base/media/java/android/media/PlaybackParams.java
|
||||
DEFAULT = 0,
|
||||
/** Selects timestretch algorithm best suitable for voice (speech) content. */
|
||||
VOICE = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Behavior when the values for speed and / or pitch are out
|
||||
* of applicable range.
|
||||
*/
|
||||
@export(name="audio_timestretch_fallback_mode_t", value_prefix="AUDIO_TIMESTRETCH_FALLBACK_")
|
||||
enum TimestretchFallbackMode : int32_t {
|
||||
// Need to be in sync with AUDIO_FALLBACK_MODE_* constants in
|
||||
// frameworks/base/media/java/android/media/PlaybackParams.java
|
||||
/** Play silence for parameter values that are out of range. */
|
||||
MUTE = 1,
|
||||
/** Return an error while trying to set the parameters. */
|
||||
FAIL = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Parameters determining playback behavior. They are used to speed up or
|
||||
* slow down playback and / or change the tonal frequency of the audio content
|
||||
* (pitch).
|
||||
*/
|
||||
struct PlaybackRate {
|
||||
/**
|
||||
* Speed factor (multiplier). Normal speed has the value of 1.0f.
|
||||
* Values less than 1.0f slow down playback, value greater than 1.0f
|
||||
* speed it up.
|
||||
*/
|
||||
float speed;
|
||||
/**
|
||||
* Pitch factor (multiplier). Setting pitch value to 1.0f together
|
||||
* with changing playback speed preserves the pitch, this is often
|
||||
* called "timestretching." Setting the pitch value equal to speed produces
|
||||
* the same effect as playing audio content at different sampling rate.
|
||||
*/
|
||||
float pitch;
|
||||
/**
|
||||
* Selects the algorithm used for timestretching (preserving pitch while
|
||||
* playing audio at different speed).
|
||||
*/
|
||||
TimestretchMode timestretchMode;
|
||||
/**
|
||||
* Selects the behavior when the specified values for speed and / or pitch
|
||||
* are out of applicable range.
|
||||
*/
|
||||
TimestretchFallbackMode fallbackMode;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
Directory structure of the audio HIDL related code.
|
||||
|
||||
Run `common/all-versions/copyHAL.sh` to create a new version of the audio HAL
|
||||
based on an existing one.
|
||||
|
||||
audio
|
||||
|-- 2.0 <== core 2.0 HIDL API. .hal can not be moved into the core directory
|
||||
| because that would change its namespace and include path
|
||||
@@ -11,13 +14,13 @@ audio
|
||||
| |-- 2.0 <== HIDL API of V2
|
||||
| |-- 4.0
|
||||
| |-- ...
|
||||
| `-- all_versions <== code common to all version of both core and effect API
|
||||
| `-- all-versions <== code common to all version of both core and effect API
|
||||
| |-- default <== implementation shared code between core and effect impl
|
||||
| |-- test <== utilities used by tests
|
||||
| `-- util <== utilities used by both implementation and tests
|
||||
|
|
||||
|-- core <== VTS and default implementation of the core API (not HIDL, see /audio/2.0))
|
||||
| `-- all_versions <== Code is version independent through #if and separate files
|
||||
| `-- all-versions <== Code is version independent through #if and separate files
|
||||
| |-- default <== code that wraps the legacy API
|
||||
| `-- vts <== vts of core API
|
||||
| |-- 2.0 <== 2.0 specific tests and helpers
|
||||
@@ -28,6 +31,6 @@ audio
|
||||
|-- 2.0
|
||||
|-- 4.0
|
||||
|-- ...
|
||||
`-- all_versions
|
||||
`-- all-versions
|
||||
|-- default
|
||||
`-- vts
|
||||
|
||||
@@ -91,37 +91,76 @@ struct Uuid {
|
||||
enum AudioStreamType : int32_t {
|
||||
// These values must kept in sync with
|
||||
// frameworks/base/media/java/android/media/AudioSystem.java
|
||||
/** Used to identify the default audio stream volume. */
|
||||
DEFAULT = -1,
|
||||
/** Specifies the minimum value for use in checks and loops. */
|
||||
MIN = 0,
|
||||
/** Used to identify the volume of audio streams for phone calls. */
|
||||
VOICE_CALL = 0,
|
||||
/** Used to identify the volume of audio streams for system sounds. */
|
||||
SYSTEM = 1,
|
||||
/**
|
||||
* Used to identify the volume of audio streams for the phone ring
|
||||
* and message alerts.
|
||||
*/
|
||||
RING = 2,
|
||||
/** Used to identify the volume of audio streams for music playback. */
|
||||
MUSIC = 3,
|
||||
/** Used to identify the volume of audio streams for alarms. */
|
||||
ALARM = 4,
|
||||
/** Used to identify the volume of audio streams for notifications. */
|
||||
NOTIFICATION = 5,
|
||||
/**
|
||||
* Used to identify the volume of audio streams for phone calls
|
||||
* when connected on bluetooth.
|
||||
*/
|
||||
BLUETOOTH_SCO = 6,
|
||||
ENFORCED_AUDIBLE = 7, // Sounds that cannot be muted by user and must be
|
||||
// routed to speaker
|
||||
/**
|
||||
* Used to identify the volume of audio streams for enforced system
|
||||
* sounds in certain countries (e.g camera in Japan). */
|
||||
ENFORCED_AUDIBLE = 7,
|
||||
/** Used to identify the volume of audio streams for DTMF tones. */
|
||||
DTMF = 8,
|
||||
TTS = 9, // Transmitted Through Speaker. Plays over speaker
|
||||
// only, silent on other devices
|
||||
ACCESSIBILITY = 10, // For accessibility talk back prompts
|
||||
ASSISTANT = 11, // For virtual assistant service
|
||||
/**
|
||||
* Used to identify the volume of audio streams exclusively transmitted
|
||||
* through the speaker (TTS) of the device.
|
||||
*/
|
||||
TTS = 9,
|
||||
/**
|
||||
* Used to identify the volume of audio streams for accessibility prompts.
|
||||
*/
|
||||
ACCESSIBILITY = 10,
|
||||
/** Used to identify the volume of audio streams for virtual assistant. */
|
||||
ASSISTANT = 11,
|
||||
};
|
||||
|
||||
@export(name="audio_source_t", value_prefix="AUDIO_SOURCE_")
|
||||
enum AudioSource : int32_t {
|
||||
// These values must kept in sync with
|
||||
// frameworks/base/media/java/android/media/MediaRecorder.java,
|
||||
// frameworks/av/services/audiopolicy/AudioPolicyService.cpp,
|
||||
// system/media/audio_effects/include/audio_effects/audio_effects_conf.h
|
||||
/** Default audio source. */
|
||||
DEFAULT = 0,
|
||||
/** Microphone audio source. */
|
||||
MIC = 1,
|
||||
/** Voice call uplink (Tx) audio source. */
|
||||
VOICE_UPLINK = 2,
|
||||
/** Voice call downlink (Rx) audio source. */
|
||||
VOICE_DOWNLINK = 3,
|
||||
/** Voice call uplink + downlink audio source. */
|
||||
VOICE_CALL = 4,
|
||||
/**
|
||||
* Microphone audio source tuned for video recording, with the same
|
||||
* orientation as the camera if available.
|
||||
*/
|
||||
CAMCORDER = 5,
|
||||
/** Microphone audio source tuned for voice recognition. */
|
||||
VOICE_RECOGNITION = 6,
|
||||
/**
|
||||
* Microphone audio source tuned for voice communications such as VoIP. It
|
||||
* will for instance take advantage of echo cancellation or automatic gain
|
||||
* control if available.
|
||||
*/
|
||||
VOICE_COMMUNICATION = 7,
|
||||
/**
|
||||
* Source for the mix to be presented remotely. An example of remote
|
||||
@@ -146,7 +185,9 @@ enum AudioSource : int32_t {
|
||||
* to include all post processing applied to the playback path.
|
||||
*/
|
||||
ECHO_REFERENCE = 1997,
|
||||
/** Virtual source for the built-in FM tuner. */
|
||||
FM_TUNER = 1998,
|
||||
/** Virtual source for the last captured hotword. */
|
||||
HOTWORD = 1999,
|
||||
};
|
||||
|
||||
@@ -562,6 +603,8 @@ enum AudioMode : int32_t {
|
||||
IN_CALL = 2,
|
||||
/** Calls handled by apps (Eg: Hangout). */
|
||||
IN_COMMUNICATION = 3,
|
||||
/** Call screening in progress. */
|
||||
CALL_SCREEN = 4,
|
||||
};
|
||||
|
||||
@export(name="", value_prefix="AUDIO_DEVICE_")
|
||||
@@ -738,6 +781,7 @@ enum AudioInputFlag : int32_t {
|
||||
MMAP_NOIRQ = 0x10, // input operates in MMAP no IRQ mode.
|
||||
VOIP_TX = 0x20, // preferred input for VoIP calls.
|
||||
HW_AV_SYNC = 0x40, // input connected to an output that uses a hardware A/V sync
|
||||
DIRECT = 0x80, // for acquiring encoded streams
|
||||
};
|
||||
|
||||
@export(name="audio_usage_t", value_prefix="AUDIO_USAGE_")
|
||||
@@ -745,19 +789,86 @@ enum AudioUsage : int32_t {
|
||||
// These values must kept in sync with
|
||||
// frameworks/base/media/java/android/media/AudioAttributes.java
|
||||
// Note that not all framework values are exposed
|
||||
/**
|
||||
* Usage value to use when the usage is unknown.
|
||||
*/
|
||||
UNKNOWN = 0,
|
||||
/**
|
||||
* Usage value to use when the usage is media, such as music, or movie
|
||||
* soundtracks.
|
||||
*/
|
||||
MEDIA = 1,
|
||||
/**
|
||||
* Usage value to use when the usage is voice communications, such as
|
||||
* telephony or VoIP.
|
||||
*/
|
||||
VOICE_COMMUNICATION = 2,
|
||||
/**
|
||||
* Usage value to use when the usage is in-call signalling, such as with
|
||||
* a "busy" beep, or DTMF tones.
|
||||
*/
|
||||
VOICE_COMMUNICATION_SIGNALLING = 3,
|
||||
/**
|
||||
* Usage value to use when the usage is an alarm (e.g. wake-up alarm).
|
||||
*/
|
||||
ALARM = 4,
|
||||
/**
|
||||
* Usage value to use when the usage is a generic notification.
|
||||
*/
|
||||
NOTIFICATION = 5,
|
||||
/**
|
||||
* Usage value to use when the usage is telephony ringtone.
|
||||
*/
|
||||
NOTIFICATION_TELEPHONY_RINGTONE = 6,
|
||||
/**
|
||||
* Usage value to use when the usage is for accessibility, such as with
|
||||
* a screen reader.
|
||||
*/
|
||||
ASSISTANCE_ACCESSIBILITY = 11,
|
||||
/**
|
||||
* Usage value to use when the usage is driving or navigation directions.
|
||||
*/
|
||||
ASSISTANCE_NAVIGATION_GUIDANCE = 12,
|
||||
/**
|
||||
* Usage value to use when the usage is sonification, such as with user
|
||||
* interface sounds.
|
||||
*/
|
||||
ASSISTANCE_SONIFICATION = 13,
|
||||
/**
|
||||
* Usage value to use when the usage is for game audio.
|
||||
*/
|
||||
GAME = 14,
|
||||
/**
|
||||
* Usage value to use when feeding audio to the platform and replacing
|
||||
* "traditional" audio source, such as audio capture devices.
|
||||
*/
|
||||
VIRTUAL_SOURCE = 15,
|
||||
/**
|
||||
* Usage value to use for audio responses to user queries, audio
|
||||
* instructions or help utterances.
|
||||
*/
|
||||
ASSISTANT = 16,
|
||||
/**
|
||||
* Usage value to use for assistant voice interaction with remote caller
|
||||
* on Cell and VoIP calls.
|
||||
*/
|
||||
CALL_ASSISTANT = 17,
|
||||
/**
|
||||
* Usage value to use when the usage is an emergency.
|
||||
*/
|
||||
EMERGENCY = 1000,
|
||||
/**
|
||||
* Usage value to use when the usage is a safety sound.
|
||||
*/
|
||||
SAFETY = 1001,
|
||||
/**
|
||||
* Usage value to use when the usage is a vehicle status.
|
||||
*/
|
||||
VEHICLE_STATUS = 1002,
|
||||
/**
|
||||
* Usage value to use when the usage is an announcement.
|
||||
*/
|
||||
ANNOUNCEMENT = 1003,
|
||||
};
|
||||
|
||||
/** Type of audio generated by an application. */
|
||||
@@ -765,13 +876,52 @@ enum AudioUsage : int32_t {
|
||||
enum AudioContentType : uint32_t {
|
||||
// Do not change these values without updating their counterparts
|
||||
// in frameworks/base/media/java/android/media/AudioAttributes.java
|
||||
/**
|
||||
* Content type value to use when the content type is unknown, or other than
|
||||
* the ones defined.
|
||||
*/
|
||||
UNKNOWN = 0,
|
||||
/**
|
||||
* Content type value to use when the content type is speech.
|
||||
*/
|
||||
SPEECH = 1,
|
||||
/**
|
||||
* Content type value to use when the content type is music.
|
||||
*/
|
||||
MUSIC = 2,
|
||||
/**
|
||||
* Content type value to use when the content type is a soundtrack,
|
||||
* typically accompanying a movie or TV program.
|
||||
*/
|
||||
MOVIE = 3,
|
||||
/**
|
||||
* Content type value to use when the content type is a sound used to
|
||||
* accompany a user action, such as a beep or sound effect expressing a key
|
||||
* click, or event, such as the type of a sound for a bonus being received
|
||||
* in a game. These sounds are mostly synthesized or short Foley sounds.
|
||||
*/
|
||||
SONIFICATION = 4,
|
||||
};
|
||||
|
||||
/** Encapsulation mode used for sending audio compressed data. */
|
||||
@export(name="audio_encapsulation_mode_t", value_prefix="AUDIO_ENCAPSULATION_MODE_")
|
||||
enum AudioEncapsulationMode : int32_t {
|
||||
// Do not change these values without updating their counterparts
|
||||
// in frameworks/base/media/java/android/media/AudioTrack.java
|
||||
/**
|
||||
* No encapsulation mode for metadata.
|
||||
*/
|
||||
NONE = 0,
|
||||
/**
|
||||
* Elementary stream payload with metadata
|
||||
*/
|
||||
ELEMENTARY_STREAM = 1,
|
||||
/**
|
||||
* Handle-based payload with metadata
|
||||
*/
|
||||
HANDLE = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Additional information about the stream passed to hardware decoders.
|
||||
*/
|
||||
@@ -787,6 +937,9 @@ struct AudioOffloadInfo {
|
||||
uint32_t bitWidth;
|
||||
uint32_t bufferSize;
|
||||
AudioUsage usage;
|
||||
AudioEncapsulationMode encapsulationMode;
|
||||
int32_t contentId;
|
||||
int32_t syncId;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,12 +28,13 @@ namespace common {
|
||||
namespace CPP_VERSION {
|
||||
namespace implementation {
|
||||
|
||||
void HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config) {
|
||||
status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config) {
|
||||
config->sampleRateHz = halConfig.sample_rate;
|
||||
config->channelMask = EnumBitfield<AudioChannelMask>(halConfig.channel_mask);
|
||||
config->format = AudioFormat(halConfig.format);
|
||||
audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo);
|
||||
status_t status = audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo);
|
||||
config->frameCount = halConfig.frame_count;
|
||||
return status;
|
||||
}
|
||||
|
||||
void HidlUtils::audioConfigToHal(const AudioConfig& config, audio_config_t* halConfig) {
|
||||
@@ -106,8 +107,8 @@ audio_usage_t HidlUtils::audioUsageToHal(const AudioUsage usage) {
|
||||
return static_cast<audio_usage_t>(usage);
|
||||
}
|
||||
|
||||
void HidlUtils::audioOffloadInfoFromHal(const audio_offload_info_t& halOffload,
|
||||
AudioOffloadInfo* offload) {
|
||||
status_t HidlUtils::audioOffloadInfoFromHal(const audio_offload_info_t& halOffload,
|
||||
AudioOffloadInfo* offload) {
|
||||
offload->sampleRateHz = halOffload.sample_rate;
|
||||
offload->channelMask = EnumBitfield<AudioChannelMask>(halOffload.channel_mask);
|
||||
offload->format = AudioFormat(halOffload.format);
|
||||
@@ -119,6 +120,26 @@ void HidlUtils::audioOffloadInfoFromHal(const audio_offload_info_t& halOffload,
|
||||
offload->bitWidth = halOffload.bit_width;
|
||||
offload->bufferSize = halOffload.offload_buffer_size;
|
||||
offload->usage = audioUsageFromHal(halOffload.usage);
|
||||
#if MAJOR_VERSION >= 6
|
||||
if (halOffload.version >= AUDIO_OFFLOAD_INFO_VERSION_0_2) {
|
||||
offload->encapsulationMode =
|
||||
static_cast<AudioEncapsulationMode>(halOffload.encapsulation_mode);
|
||||
offload->contentId = halOffload.content_id;
|
||||
offload->syncId = halOffload.sync_id;
|
||||
} else {
|
||||
offload->encapsulationMode = AudioEncapsulationMode::NONE;
|
||||
offload->contentId = 0;
|
||||
offload->syncId = 0;
|
||||
}
|
||||
#else
|
||||
// nonzero values here are not compatible with HAL versions below 6.
|
||||
if (halOffload.version >= AUDIO_OFFLOAD_INFO_VERSION_0_2 &&
|
||||
(halOffload.encapsulation_mode != AUDIO_ENCAPSULATION_MODE_NONE ||
|
||||
halOffload.content_id != 0 || halOffload.sync_id != 0)) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
#endif
|
||||
return OK;
|
||||
}
|
||||
|
||||
void HidlUtils::audioOffloadInfoToHal(const AudioOffloadInfo& offload,
|
||||
@@ -135,6 +156,14 @@ void HidlUtils::audioOffloadInfoToHal(const AudioOffloadInfo& offload,
|
||||
halOffload->bit_width = offload.bitWidth;
|
||||
halOffload->offload_buffer_size = offload.bufferSize;
|
||||
halOffload->usage = audioUsageToHal(offload.usage);
|
||||
#if MAJOR_VERSION >= 6
|
||||
halOffload->encapsulation_mode =
|
||||
static_cast<audio_encapsulation_mode_t>(offload.encapsulationMode);
|
||||
halOffload->content_id = offload.contentId;
|
||||
halOffload->sync_id = offload.syncId;
|
||||
#else
|
||||
// offload doesn't contain encapsulationMode, contentId, syncId, so this is OK.
|
||||
#endif
|
||||
}
|
||||
|
||||
void HidlUtils::audioPortConfigFromHal(const struct audio_port_config& halConfig,
|
||||
|
||||
@@ -35,8 +35,11 @@ namespace implementation {
|
||||
using namespace ::android::hardware::audio::common::CPP_VERSION;
|
||||
|
||||
class HidlUtils {
|
||||
public:
|
||||
static void audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config);
|
||||
public:
|
||||
// A failure here indicates a platform config that is incompatible with
|
||||
// the compiled HIDL interface version.
|
||||
static status_t audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config);
|
||||
|
||||
static void audioConfigToHal(const AudioConfig& config, audio_config_t* halConfig);
|
||||
static void audioGainConfigFromHal(const struct audio_gain_config& halConfig,
|
||||
AudioGainConfig* config);
|
||||
@@ -46,8 +49,10 @@ class HidlUtils {
|
||||
static void audioGainToHal(const AudioGain& gain, struct audio_gain* halGain);
|
||||
static AudioUsage audioUsageFromHal(const audio_usage_t halUsage);
|
||||
static audio_usage_t audioUsageToHal(const AudioUsage usage);
|
||||
static void audioOffloadInfoFromHal(const audio_offload_info_t& halOffload,
|
||||
AudioOffloadInfo* offload);
|
||||
// A failure here indicates a platform offload info that is incompatible with
|
||||
// the compiled HIDL interface version.
|
||||
static status_t audioOffloadInfoFromHal(const audio_offload_info_t& halOffload,
|
||||
AudioOffloadInfo* offload);
|
||||
static void audioOffloadInfoToHal(const AudioOffloadInfo& offload,
|
||||
audio_offload_info_t* halOffload);
|
||||
static void audioPortConfigFromHal(const struct audio_port_config& halConfig,
|
||||
@@ -58,7 +63,7 @@ class HidlUtils {
|
||||
const struct audio_port_config* halConfigs,
|
||||
hidl_vec<AudioPortConfig>* configs);
|
||||
static std::unique_ptr<audio_port_config[]> audioPortConfigsToHal(
|
||||
const hidl_vec<AudioPortConfig>& configs);
|
||||
const hidl_vec<AudioPortConfig>& configs);
|
||||
static void audioPortFromHal(const struct audio_port& halPort, AudioPort* port);
|
||||
static void audioPortToHal(const AudioPort& port, struct audio_port* halPort);
|
||||
static void uuidFromHal(const audio_uuid_t& halUuid, Uuid* uuid);
|
||||
|
||||
@@ -80,6 +80,7 @@ int main(int /* argc */, char* /* argv */ []) {
|
||||
const std::vector<InterfacesList> optionalInterfaces = {
|
||||
{
|
||||
"Soundtrigger API",
|
||||
"android.hardware.soundtrigger@2.3::ISoundTriggerHw",
|
||||
"android.hardware.soundtrigger@2.2::ISoundTriggerHw",
|
||||
"android.hardware.soundtrigger@2.1::ISoundTriggerHw",
|
||||
"android.hardware.soundtrigger@2.0::ISoundTriggerHw",
|
||||
|
||||
@@ -171,7 +171,8 @@ std::tuple<Result, sp<IStreamOut>> Device::openOutputStreamImpl(int32_t ioHandle
|
||||
streamOut = new StreamOut(this, halStream);
|
||||
++mOpenedStreamsCount;
|
||||
}
|
||||
HidlUtils::audioConfigFromHal(halConfig, suggestedConfig);
|
||||
status_t convertStatus = HidlUtils::audioConfigFromHal(halConfig, suggestedConfig);
|
||||
ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__);
|
||||
return {analyzeStatus("open_output_stream", status, {EINVAL} /*ignore*/), streamOut};
|
||||
}
|
||||
|
||||
@@ -198,7 +199,8 @@ std::tuple<Result, sp<IStreamIn>> Device::openInputStreamImpl(
|
||||
streamIn = new StreamIn(this, halStream);
|
||||
++mOpenedStreamsCount;
|
||||
}
|
||||
HidlUtils::audioConfigFromHal(halConfig, suggestedConfig);
|
||||
status_t convertStatus = HidlUtils::audioConfigFromHal(halConfig, suggestedConfig);
|
||||
ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__);
|
||||
return {analyzeStatus("open_input_stream", status, {EINVAL} /*ignore*/), streamIn};
|
||||
}
|
||||
|
||||
@@ -269,12 +271,21 @@ Return<bool> Device::supportsAudioPatches() {
|
||||
Return<void> Device::createAudioPatch(const hidl_vec<AudioPortConfig>& sources,
|
||||
const hidl_vec<AudioPortConfig>& sinks,
|
||||
createAudioPatch_cb _hidl_cb) {
|
||||
auto [retval, patch] = createOrUpdateAudioPatch(
|
||||
static_cast<AudioPatchHandle>(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE), sources,
|
||||
sinks);
|
||||
_hidl_cb(retval, patch);
|
||||
return Void();
|
||||
}
|
||||
|
||||
std::tuple<Result, AudioPatchHandle> Device::createOrUpdateAudioPatch(
|
||||
AudioPatchHandle patch, const hidl_vec<AudioPortConfig>& sources,
|
||||
const hidl_vec<AudioPortConfig>& sinks) {
|
||||
Result retval(Result::NOT_SUPPORTED);
|
||||
AudioPatchHandle patch = 0;
|
||||
if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
|
||||
std::unique_ptr<audio_port_config[]> halSources(HidlUtils::audioPortConfigsToHal(sources));
|
||||
std::unique_ptr<audio_port_config[]> halSinks(HidlUtils::audioPortConfigsToHal(sinks));
|
||||
audio_patch_handle_t halPatch = AUDIO_PATCH_HANDLE_NONE;
|
||||
audio_patch_handle_t halPatch = static_cast<audio_patch_handle_t>(patch);
|
||||
retval = analyzeStatus("create_audio_patch",
|
||||
mDevice->create_audio_patch(mDevice, sources.size(), &halSources[0],
|
||||
sinks.size(), &halSinks[0], &halPatch));
|
||||
@@ -282,8 +293,7 @@ Return<void> Device::createAudioPatch(const hidl_vec<AudioPortConfig>& sources,
|
||||
patch = static_cast<AudioPatchHandle>(halPatch);
|
||||
}
|
||||
}
|
||||
_hidl_cb(retval, patch);
|
||||
return Void();
|
||||
return {retval, patch};
|
||||
}
|
||||
|
||||
Return<Result> Device::releaseAudioPatch(int32_t patch) {
|
||||
@@ -438,6 +448,19 @@ Return<Result> Device::removeDeviceEffect(AudioPortHandle device, uint64_t effec
|
||||
}
|
||||
}
|
||||
|
||||
Return<void> Device::updateAudioPatch(int32_t previousPatch,
|
||||
const hidl_vec<AudioPortConfig>& sources,
|
||||
const hidl_vec<AudioPortConfig>& sinks,
|
||||
createAudioPatch_cb _hidl_cb) {
|
||||
if (previousPatch != static_cast<int32_t>(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE)) {
|
||||
auto [retval, patch] = createOrUpdateAudioPatch(previousPatch, sources, sinks);
|
||||
_hidl_cb(retval, patch);
|
||||
} else {
|
||||
_hidl_cb(Result::INVALID_ARGUMENTS, previousPatch);
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace implementation
|
||||
|
||||
@@ -176,6 +176,13 @@ Return<Result> PrimaryDevice::addDeviceEffect(AudioPortHandle device, uint64_t e
|
||||
Return<Result> PrimaryDevice::removeDeviceEffect(AudioPortHandle device, uint64_t effectId) {
|
||||
return mDevice->removeDeviceEffect(device, effectId);
|
||||
}
|
||||
|
||||
Return<void> PrimaryDevice::updateAudioPatch(int32_t previousPatch,
|
||||
const hidl_vec<AudioPortConfig>& sources,
|
||||
const hidl_vec<AudioPortConfig>& sinks,
|
||||
updateAudioPatch_cb _hidl_cb) {
|
||||
return mDevice->updateAudioPatch(previousPatch, sources, sinks, _hidl_cb);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Methods from ::android::hardware::audio::CPP_VERSION::IPrimaryDevice follow.
|
||||
@@ -196,6 +203,9 @@ Return<Result> PrimaryDevice::setMode(AudioMode mode) {
|
||||
case AudioMode::RINGTONE:
|
||||
case AudioMode::IN_CALL:
|
||||
case AudioMode::IN_COMMUNICATION:
|
||||
#if MAJOR_VERSION >= 6
|
||||
case AudioMode::CALL_SCREEN:
|
||||
#endif
|
||||
break; // Valid values
|
||||
default:
|
||||
return Result::INVALID_ARGUMENTS;
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
//#define LOG_NDEBUG 0
|
||||
#define ATRACE_TAG ATRACE_TAG_AUDIO
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <android/log.h>
|
||||
@@ -453,20 +455,22 @@ int StreamOut::asyncCallback(stream_callback_event_t event, void*, void* cookie)
|
||||
sp<IStreamOutCallback> callback = self->mCallback;
|
||||
if (callback.get() == nullptr) return 0;
|
||||
ALOGV("asyncCallback() event %d", event);
|
||||
Return<void> result;
|
||||
switch (event) {
|
||||
case STREAM_CBK_EVENT_WRITE_READY:
|
||||
callback->onWriteReady();
|
||||
result = callback->onWriteReady();
|
||||
break;
|
||||
case STREAM_CBK_EVENT_DRAIN_READY:
|
||||
callback->onDrainReady();
|
||||
result = callback->onDrainReady();
|
||||
break;
|
||||
case STREAM_CBK_EVENT_ERROR:
|
||||
callback->onError();
|
||||
result = callback->onError();
|
||||
break;
|
||||
default:
|
||||
ALOGW("asyncCallback() unknown event %d", event);
|
||||
break;
|
||||
}
|
||||
ALOGW_IF(!result.isOk(), "Client callback failed: %s", result.description().c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -582,6 +586,67 @@ Return<Result> StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MAJOR_VERSION >= 6
|
||||
Return<void> StreamOut::getDualMonoMode(getDualMonoMode_cb _hidl_cb) {
|
||||
_hidl_cb(Result::NOT_SUPPORTED, DualMonoMode::OFF);
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<Result> StreamOut::setDualMonoMode(DualMonoMode /*mode*/) {
|
||||
return Result::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
Return<void> StreamOut::getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) {
|
||||
_hidl_cb(Result::NOT_SUPPORTED, -std::numeric_limits<float>::infinity());
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<Result> StreamOut::setAudioDescriptionMixLevel(float /*leveldB*/) {
|
||||
return Result::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
Return<void> StreamOut::getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) {
|
||||
_hidl_cb(Result::NOT_SUPPORTED,
|
||||
// Same as AUDIO_PLAYBACK_RATE_INITIALIZER
|
||||
PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::FAIL});
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<Result> StreamOut::setPlaybackRateParameters(const PlaybackRate& /*playbackRate*/) {
|
||||
return Result::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
Return<Result> StreamOut::setEventCallback(const sp<IStreamOutEventCallback>& callback) {
|
||||
if (mStream->set_event_callback == nullptr) return Result::NOT_SUPPORTED;
|
||||
int result = mStream->set_event_callback(mStream, StreamOut::asyncEventCallback, this);
|
||||
if (result == 0) {
|
||||
mEventCallback = callback;
|
||||
}
|
||||
return Stream::analyzeStatus("set_stream_out_callback", result, {ENOSYS} /*ignore*/);
|
||||
}
|
||||
|
||||
// static
|
||||
int StreamOut::asyncEventCallback(stream_event_callback_type_t event, void* param, void* cookie) {
|
||||
StreamOut* self = reinterpret_cast<StreamOut*>(cookie);
|
||||
sp<IStreamOutEventCallback> eventCallback = self->mEventCallback;
|
||||
if (eventCallback.get() == nullptr) return 0;
|
||||
ALOGV("%s event %d", __func__, event);
|
||||
Return<void> result;
|
||||
switch (event) {
|
||||
case STREAM_EVENT_CBK_TYPE_CODEC_FORMAT_CHANGED: {
|
||||
hidl_vec<uint8_t> audioMetadata;
|
||||
audioMetadata.setToExternal((uint8_t*)param, strlen((char*)param));
|
||||
result = eventCallback->onCodecFormatChanged(audioMetadata);
|
||||
} break;
|
||||
default:
|
||||
ALOGW("%s unknown event %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
ALOGW_IF(!result.isOk(), "Client callback failed: %s", result.description().c_str());
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace CPP_VERSION
|
||||
} // namespace audio
|
||||
|
||||
@@ -118,6 +118,9 @@ struct Device : public IDevice, public ParametersUtil {
|
||||
Return<Result> close() override;
|
||||
Return<Result> addDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
|
||||
Return<Result> removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
|
||||
Return<void> updateAudioPatch(int32_t previousPatch, const hidl_vec<AudioPortConfig>& sources,
|
||||
const hidl_vec<AudioPortConfig>& sinks,
|
||||
createAudioPatch_cb _hidl_cb) override;
|
||||
#endif
|
||||
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
|
||||
|
||||
@@ -136,6 +139,9 @@ struct Device : public IDevice, public ParametersUtil {
|
||||
virtual ~Device();
|
||||
|
||||
Result doClose();
|
||||
std::tuple<Result, AudioPatchHandle> createOrUpdateAudioPatch(
|
||||
AudioPatchHandle patch, const hidl_vec<AudioPortConfig>& sources,
|
||||
const hidl_vec<AudioPortConfig>& sinks);
|
||||
|
||||
// Methods from ParametersUtil.
|
||||
char* halGetParameters(const char* keys) override;
|
||||
|
||||
@@ -100,6 +100,9 @@ struct PrimaryDevice : public IPrimaryDevice {
|
||||
Return<Result> close() override;
|
||||
Return<Result> addDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
|
||||
Return<Result> removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
|
||||
Return<void> updateAudioPatch(int32_t previousPatch, const hidl_vec<AudioPortConfig>& sources,
|
||||
const hidl_vec<AudioPortConfig>& sinks,
|
||||
updateAudioPatch_cb _hidl_cb) override;
|
||||
#endif
|
||||
|
||||
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
|
||||
|
||||
@@ -157,6 +157,10 @@ Return<void> StreamMmap<T>::createMmapBuffer(int32_t minSizeFrames, size_t frame
|
||||
native_handle_t* hidlHandle = nullptr;
|
||||
|
||||
if (mStream->create_mmap_buffer != NULL) {
|
||||
if (minSizeFrames <= 0) {
|
||||
retval = Result::INVALID_ARGUMENTS;
|
||||
goto exit;
|
||||
}
|
||||
struct audio_mmap_buffer_info halInfo;
|
||||
retval = Stream::analyzeStatus(
|
||||
"create_mmap_buffer", mStream->create_mmap_buffer(mStream, minSizeFrames, &halInfo));
|
||||
@@ -184,6 +188,7 @@ Return<void> StreamMmap<T>::createMmapBuffer(int32_t minSizeFrames, size_t frame
|
||||
info.burstSizeFrames = halInfo.burst_size_frames;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
_hidl_cb(retval, info);
|
||||
if (hidlHandle != nullptr) {
|
||||
native_handle_delete(hidlHandle);
|
||||
|
||||
@@ -121,16 +121,31 @@ struct StreamOut : public IStreamOut {
|
||||
Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
|
||||
Return<Result> selectPresentation(int32_t presentationId, int32_t programId) override;
|
||||
#endif
|
||||
#if MAJOR_VERSION >= 6
|
||||
Return<void> getDualMonoMode(getDualMonoMode_cb _hidl_cb) override;
|
||||
Return<Result> setDualMonoMode(DualMonoMode mode) override;
|
||||
Return<void> getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) override;
|
||||
Return<Result> setAudioDescriptionMixLevel(float leveldB) override;
|
||||
Return<void> getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) override;
|
||||
Return<Result> setPlaybackRateParameters(const PlaybackRate& playbackRate) override;
|
||||
#endif
|
||||
|
||||
static Result getPresentationPositionImpl(audio_stream_out_t* stream, uint64_t* frames,
|
||||
TimeSpec* timeStamp);
|
||||
|
||||
private:
|
||||
#if MAJOR_VERSION >= 6
|
||||
Return<Result> setEventCallback(const sp<IStreamOutEventCallback>& callback) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
const sp<Device> mDevice;
|
||||
audio_stream_out_t* mStream;
|
||||
const sp<Stream> mStreamCommon;
|
||||
const sp<StreamMmap<audio_stream_out_t>> mStreamMmap;
|
||||
sp<IStreamOutCallback> mCallback;
|
||||
sp<IStreamOutCallback> mCallback; // Callback for non-blocking write and drain
|
||||
#if MAJOR_VERSION >= 6
|
||||
sp<IStreamOutEventCallback> mEventCallback;
|
||||
#endif
|
||||
std::unique_ptr<CommandMQ> mCommandMQ;
|
||||
std::unique_ptr<DataMQ> mDataMQ;
|
||||
std::unique_ptr<StatusMQ> mStatusMQ;
|
||||
@@ -141,6 +156,10 @@ struct StreamOut : public IStreamOut {
|
||||
virtual ~StreamOut();
|
||||
|
||||
static int asyncCallback(stream_callback_event_t event, void* param, void* cookie);
|
||||
|
||||
#if MAJOR_VERSION >= 6
|
||||
static int asyncEventCallback(stream_event_callback_type_t event, void* param, void* cookie);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace implementation
|
||||
|
||||
@@ -244,7 +244,13 @@ TEST_P(OutputStreamTest, updateSourceMetadata) {
|
||||
TEST_P(AudioPrimaryHidlTest, setMode) {
|
||||
doc::test("Make sure setMode always succeeds if mode is valid and fails otherwise");
|
||||
// Test Invalid values
|
||||
for (int mode : {-2, -1, int(AudioMode::IN_COMMUNICATION) + 1}) {
|
||||
#if MAJOR_VERSION >= 6
|
||||
int maxMode = int(AudioMode::CALL_SCREEN);
|
||||
#else
|
||||
int maxMode = int(AudioMode::IN_COMMUNICATION);
|
||||
#endif
|
||||
|
||||
for (int mode : {-2, -1, maxMode + 1}) {
|
||||
ASSERT_RESULT(Result::INVALID_ARGUMENTS, getDevice()->setMode(AudioMode(mode)))
|
||||
<< "mode=" << mode;
|
||||
}
|
||||
@@ -253,6 +259,10 @@ TEST_P(AudioPrimaryHidlTest, setMode) {
|
||||
AudioMode::NORMAL /* Make sure to leave the test in normal mode */}) {
|
||||
ASSERT_OK(getDevice()->setMode(mode)) << "mode=" << toString(mode);
|
||||
}
|
||||
// AudioMode::CALL_SCREEN as support is optional
|
||||
#if MAJOR_VERSION >= 6
|
||||
ASSERT_RESULT(okOrNotSupportedOrInvalidArgs, getDevice()->setMode(AudioMode::CALL_SCREEN));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_P(AudioPrimaryHidlTest, setBtHfpSampleRate) {
|
||||
|
||||
@@ -133,3 +133,91 @@ TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) {
|
||||
ASSERT_OK(getDevice()->close());
|
||||
ASSERT_TRUE(resetDevice());
|
||||
}
|
||||
|
||||
TEST_P(AudioPatchHidlTest, UpdatePatchInvalidHandle) {
|
||||
doc::test("Verify that passing an invalid handle to updateAudioPatch is checked");
|
||||
AudioPatchHandle ignored;
|
||||
ASSERT_OK(getDevice()->updateAudioPatch(
|
||||
static_cast<int32_t>(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE),
|
||||
hidl_vec<AudioPortConfig>(), hidl_vec<AudioPortConfig>(), returnIn(res, ignored)));
|
||||
ASSERT_RESULT(Result::INVALID_ARGUMENTS, res);
|
||||
}
|
||||
|
||||
using DualMonoModeAccessorHidlTest = AccessorHidlTest<DualMonoMode, OutputStreamTest>;
|
||||
TEST_P(DualMonoModeAccessorHidlTest, DualMonoModeTest) {
|
||||
doc::test("Check that dual mono mode can be set and retrieved");
|
||||
testAccessors<OPTIONAL>(&OutputStreamTest::getStream, "dual mono mode",
|
||||
Initial{DualMonoMode::OFF},
|
||||
{DualMonoMode::LR, DualMonoMode::LL, DualMonoMode::RR},
|
||||
&IStreamOut::setDualMonoMode, &IStreamOut::getDualMonoMode);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DualMonoModeHidl, DualMonoModeAccessorHidlTest,
|
||||
::testing::ValuesIn(getOutputDeviceConfigParameters()),
|
||||
&DeviceConfigParameterToString);
|
||||
|
||||
using AudioDescriptionMixLevelHidlTest = AccessorHidlTest<float, OutputStreamTest>;
|
||||
TEST_P(AudioDescriptionMixLevelHidlTest, AudioDescriptionMixLevelTest) {
|
||||
doc::test("Check that audio description mix level can be set and retrieved");
|
||||
testAccessors<OPTIONAL>(
|
||||
&OutputStreamTest::getStream, "audio description mix level",
|
||||
Initial{-std::numeric_limits<float>::infinity()}, {-48.0f, -1.0f, 0.0f, 1.0f, 48.0f},
|
||||
&IStreamOut::setAudioDescriptionMixLevel, &IStreamOut::getAudioDescriptionMixLevel,
|
||||
{48.5f, 1000.0f, std::numeric_limits<float>::infinity()});
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(AudioDescriptionMixLevelHidl, AudioDescriptionMixLevelHidlTest,
|
||||
::testing::ValuesIn(getOutputDeviceConfigParameters()),
|
||||
&DeviceConfigParameterToString);
|
||||
|
||||
using PlaybackRateParametersHidlTest = AccessorHidlTest<PlaybackRate, OutputStreamTest>;
|
||||
TEST_P(PlaybackRateParametersHidlTest, PlaybackRateParametersTest) {
|
||||
doc::test("Check that playback rate parameters can be set and retrieved");
|
||||
testAccessors<OPTIONAL>(
|
||||
&OutputStreamTest::getStream, "playback rate parameters",
|
||||
Initial{PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT,
|
||||
TimestretchFallbackMode::FAIL}},
|
||||
{// Speed and pitch values in the range from 0.5f to 2.0f must be supported
|
||||
// (see the definition of IStreamOut::setPlaybackRateParameters).
|
||||
PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::MUTE},
|
||||
PlaybackRate{2.0f, 2.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::MUTE},
|
||||
PlaybackRate{0.5f, 0.5f, TimestretchMode::DEFAULT, TimestretchFallbackMode::MUTE},
|
||||
// Gross speed / pitch values must not be rejected if the fallback mode is "mute"
|
||||
PlaybackRate{1000.0f, 1000.0f, TimestretchMode::DEFAULT,
|
||||
TimestretchFallbackMode::MUTE},
|
||||
// Default speed / pitch values must not be rejected in "fail" fallback mode
|
||||
PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::FAIL},
|
||||
// Same for "voice" mode
|
||||
PlaybackRate{1.0f, 1.0f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE},
|
||||
PlaybackRate{2.0f, 2.0f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE},
|
||||
PlaybackRate{0.5f, 0.5f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE},
|
||||
PlaybackRate{1000.0f, 1000.0f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE},
|
||||
PlaybackRate{1.0f, 1.0f, TimestretchMode::VOICE, TimestretchFallbackMode::FAIL}},
|
||||
&IStreamOut::setPlaybackRateParameters, &IStreamOut::getPlaybackRateParameters,
|
||||
{PlaybackRate{1000.0f, 1000.0f, TimestretchMode::DEFAULT,
|
||||
TimestretchFallbackMode::FAIL},
|
||||
PlaybackRate{1000.0f, 1000.0f, TimestretchMode::VOICE,
|
||||
TimestretchFallbackMode::FAIL}});
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PlaybackRateParametersHidl, PlaybackRateParametersHidlTest,
|
||||
::testing::ValuesIn(getOutputDeviceConfigParameters()),
|
||||
&DeviceConfigParameterToString);
|
||||
|
||||
/** Stub implementation of IStreamOutEventCallback **/
|
||||
class MockOutEventCallbacks : public IStreamOutEventCallback {
|
||||
Return<void> onCodecFormatChanged(const hidl_vec<uint8_t>& audioMetadata __unused) override {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(OutputStreamTest, SetEventCallback) {
|
||||
doc::test("If supported, set event callback for output stream should never fail");
|
||||
auto res = stream->setEventCallback(new MockOutEventCallbacks);
|
||||
EXPECT_RESULT(okOrNotSupported, res);
|
||||
if (res == Result::OK) {
|
||||
ASSERT_OK(stream->setEventCallback(nullptr));
|
||||
} else {
|
||||
doc::partialTest("The stream does not support event callback");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,11 @@ static auto invalidStateOrNotSupported = {Result::INVALID_STATE, Result::NOT_SUP
|
||||
class HidlTest : public ::testing::Test {
|
||||
public:
|
||||
virtual ~HidlTest() = default;
|
||||
// public access to avoid annoyances when using this method in template classes
|
||||
// derived from test classes
|
||||
sp<IDevice> getDevice() const {
|
||||
return DeviceManager::getInstance().get(getFactoryName(), getDeviceName());
|
||||
}
|
||||
|
||||
protected:
|
||||
// Factory and device name getters to be overridden in subclasses.
|
||||
@@ -118,9 +123,6 @@ class HidlTest : public ::testing::Test {
|
||||
sp<IDevicesFactory> getDevicesFactory() const {
|
||||
return DevicesFactoryManager::getInstance().get(getFactoryName());
|
||||
}
|
||||
sp<IDevice> getDevice() const {
|
||||
return DeviceManager::getInstance().get(getFactoryName(), getDeviceName());
|
||||
}
|
||||
bool resetDevice() const {
|
||||
return DeviceManager::getInstance().reset(getFactoryName(), getDeviceName());
|
||||
}
|
||||
@@ -411,7 +413,8 @@ class AudioPrimaryHidlTest : public AudioHidlDeviceTest {
|
||||
ASSERT_TRUE(getDevice() != nullptr);
|
||||
}
|
||||
|
||||
protected:
|
||||
// public access to avoid annoyances when using this method in template classes
|
||||
// derived from test classes
|
||||
sp<IPrimaryDevice> getDevice() const {
|
||||
return DeviceManager::getInstance().getPrimary(getFactoryName());
|
||||
}
|
||||
@@ -445,15 +448,15 @@ class AccessorHidlTest : public BaseTestClass {
|
||||
/** Test a property getter and setter.
|
||||
* The getter and/or the setter may return NOT_SUPPORTED if optionality == OPTIONAL.
|
||||
*/
|
||||
template <Optionality optionality = REQUIRED, class Getter, class Setter>
|
||||
void testAccessors(const string& propertyName, const Initial expectedInitial,
|
||||
list<Property> valuesToTest, Setter setter, Getter getter,
|
||||
const vector<Property>& invalidValues = {}) {
|
||||
template <Optionality optionality = REQUIRED, class IUTGetter, class Getter, class Setter>
|
||||
void testAccessors(IUTGetter iutGetter, const string& propertyName,
|
||||
const Initial expectedInitial, list<Property> valuesToTest, Setter setter,
|
||||
Getter getter, const vector<Property>& invalidValues = {}) {
|
||||
const auto expectedResults = {Result::OK,
|
||||
optionality == OPTIONAL ? Result::NOT_SUPPORTED : Result::OK};
|
||||
|
||||
Property initialValue = expectedInitial.value;
|
||||
ASSERT_OK((BaseTestClass::getDevice().get()->*getter)(returnIn(res, initialValue)));
|
||||
ASSERT_OK(((this->*iutGetter)().get()->*getter)(returnIn(res, initialValue)));
|
||||
ASSERT_RESULT(expectedResults, res);
|
||||
if (res == Result::OK && expectedInitial.check == REQUIRED) {
|
||||
EXPECT_EQ(expectedInitial.value, initialValue);
|
||||
@@ -464,7 +467,7 @@ class AccessorHidlTest : public BaseTestClass {
|
||||
for (Property setValue : valuesToTest) {
|
||||
SCOPED_TRACE("Test " + propertyName + " getter and setter for " +
|
||||
testing::PrintToString(setValue));
|
||||
auto ret = (BaseTestClass::getDevice().get()->*setter)(setValue);
|
||||
auto ret = ((this->*iutGetter)().get()->*setter)(setValue);
|
||||
ASSERT_RESULT(expectedResults, ret);
|
||||
if (ret == Result::NOT_SUPPORTED) {
|
||||
doc::partialTest(propertyName + " setter is not supported");
|
||||
@@ -472,7 +475,7 @@ class AccessorHidlTest : public BaseTestClass {
|
||||
}
|
||||
Property getValue;
|
||||
// Make sure the getter returns the same value just set
|
||||
ASSERT_OK((BaseTestClass::getDevice().get()->*getter)(returnIn(res, getValue)));
|
||||
ASSERT_OK(((this->*iutGetter)().get()->*getter)(returnIn(res, getValue)));
|
||||
ASSERT_RESULT(expectedResults, res);
|
||||
if (res == Result::NOT_SUPPORTED) {
|
||||
doc::partialTest(propertyName + " getter is not supported");
|
||||
@@ -485,11 +488,18 @@ class AccessorHidlTest : public BaseTestClass {
|
||||
SCOPED_TRACE("Try to set " + propertyName + " with the invalid value " +
|
||||
testing::PrintToString(invalidValue));
|
||||
EXPECT_RESULT(invalidArgsOrNotSupported,
|
||||
(BaseTestClass::getDevice().get()->*setter)(invalidValue));
|
||||
((this->*iutGetter)().get()->*setter)(invalidValue));
|
||||
}
|
||||
|
||||
// Restore initial value
|
||||
EXPECT_RESULT(expectedResults, (BaseTestClass::getDevice().get()->*setter)(initialValue));
|
||||
EXPECT_RESULT(expectedResults, ((this->*iutGetter)().get()->*setter)(initialValue));
|
||||
}
|
||||
template <Optionality optionality = REQUIRED, class Getter, class Setter>
|
||||
void testAccessors(const string& propertyName, const Initial expectedInitial,
|
||||
list<Property> valuesToTest, Setter setter, Getter getter,
|
||||
const vector<Property>& invalidValues = {}) {
|
||||
testAccessors<optionality>(&BaseTestClass::getDevice, propertyName, expectedInitial,
|
||||
valuesToTest, setter, getter, invalidValues);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -881,6 +891,11 @@ class StreamHelper {
|
||||
|
||||
template <class Stream>
|
||||
class OpenStreamTest : public AudioHidlTestWithDeviceConfigParameter {
|
||||
public:
|
||||
// public access to avoid annoyances when using this method in template classes
|
||||
// derived from test classes
|
||||
sp<Stream> getStream() const { return stream; }
|
||||
|
||||
protected:
|
||||
OpenStreamTest() : AudioHidlTestWithDeviceConfigParameter(), helper(stream) {}
|
||||
template <class Open>
|
||||
|
||||
@@ -307,12 +307,11 @@ void Effect::getConfigImpl(int commandCode, const char* commandName, GetConfigCa
|
||||
Result Effect::getCurrentConfigImpl(uint32_t featureId, uint32_t configSize,
|
||||
GetCurrentConfigSuccessCallback onSuccess) {
|
||||
uint32_t halCmd = featureId;
|
||||
uint32_t halResult[alignedSizeIn<uint32_t>(sizeof(uint32_t) + configSize)];
|
||||
memset(halResult, 0, sizeof(halResult));
|
||||
std::vector<uint32_t> halResult(alignedSizeIn<uint32_t>(sizeof(uint32_t) + configSize), 0);
|
||||
uint32_t halResultSize = 0;
|
||||
return sendCommandReturningStatusAndData(EFFECT_CMD_GET_FEATURE_CONFIG, "GET_FEATURE_CONFIG",
|
||||
sizeof(uint32_t), &halCmd, &halResultSize, halResult,
|
||||
sizeof(uint32_t), [&] { onSuccess(&halResult[1]); });
|
||||
return sendCommandReturningStatusAndData(
|
||||
EFFECT_CMD_GET_FEATURE_CONFIG, "GET_FEATURE_CONFIG", sizeof(uint32_t), &halCmd,
|
||||
&halResultSize, &halResult[0], sizeof(uint32_t), [&] { onSuccess(&halResult[1]); });
|
||||
}
|
||||
|
||||
Result Effect::getParameterImpl(uint32_t paramSize, const void* paramData,
|
||||
@@ -339,8 +338,7 @@ Result Effect::getSupportedConfigsImpl(uint32_t featureId, uint32_t maxConfigs,
|
||||
GetSupportedConfigsSuccessCallback onSuccess) {
|
||||
uint32_t halCmd[2] = {featureId, maxConfigs};
|
||||
uint32_t halResultSize = 2 * sizeof(uint32_t) + maxConfigs * sizeof(configSize);
|
||||
uint8_t halResult[halResultSize];
|
||||
memset(&halResult[0], 0, halResultSize);
|
||||
std::vector<uint8_t> halResult(static_cast<size_t>(halResultSize), 0);
|
||||
return sendCommandReturningStatusAndData(
|
||||
EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS, "GET_FEATURE_SUPPORTED_CONFIGS", sizeof(halCmd),
|
||||
halCmd, &halResultSize, &halResult[0], 2 * sizeof(uint32_t), [&] {
|
||||
@@ -519,9 +517,9 @@ Return<void> Effect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
|
||||
uint32_t halDataSize;
|
||||
std::unique_ptr<uint8_t[]> halData = hidlVecToHal(volumes, &halDataSize);
|
||||
uint32_t halResultSize = halDataSize;
|
||||
uint32_t halResult[volumes.size()];
|
||||
std::vector<uint32_t> halResult(volumes.size(), 0);
|
||||
Result retval = sendCommandReturningData(EFFECT_CMD_SET_VOLUME, "SET_VOLUME", halDataSize,
|
||||
&halData[0], &halResultSize, halResult);
|
||||
&halData[0], &halResultSize, &halResult[0]);
|
||||
hidl_vec<uint32_t> result;
|
||||
if (retval == Result::OK) {
|
||||
result.setToExternal(&halResult[0], halResultSize);
|
||||
@@ -581,8 +579,6 @@ Return<void> Effect::getSupportedAuxChannelsConfigs(uint32_t maxConfigs,
|
||||
}
|
||||
|
||||
Return<void> Effect::getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) {
|
||||
uint32_t halResult[alignedSizeIn<uint32_t>(sizeof(uint32_t) + sizeof(channel_config_t))];
|
||||
memset(halResult, 0, sizeof(halResult));
|
||||
EffectAuxChannelsConfig result;
|
||||
Result retval = getCurrentConfigImpl(
|
||||
EFFECT_FEATURE_AUX_CHANNELS, sizeof(channel_config_t), [&](void* configData) {
|
||||
@@ -594,11 +590,12 @@ Return<void> Effect::getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) {
|
||||
}
|
||||
|
||||
Return<Result> Effect::setAuxChannelsConfig(const EffectAuxChannelsConfig& config) {
|
||||
uint32_t halCmd[alignedSizeIn<uint32_t>(sizeof(uint32_t) + sizeof(channel_config_t))];
|
||||
std::vector<uint32_t> halCmd(
|
||||
alignedSizeIn<uint32_t>(sizeof(uint32_t) + sizeof(channel_config_t)), 0);
|
||||
halCmd[0] = EFFECT_FEATURE_AUX_CHANNELS;
|
||||
effectAuxChannelsConfigToHal(config, reinterpret_cast<channel_config_t*>(&halCmd[1]));
|
||||
return sendCommandReturningStatus(EFFECT_CMD_SET_FEATURE_CONFIG,
|
||||
"SET_FEATURE_CONFIG AUX_CHANNELS", sizeof(halCmd), halCmd);
|
||||
"SET_FEATURE_CONFIG AUX_CHANNELS", halCmd.size(), &halCmd[0]);
|
||||
}
|
||||
|
||||
Return<Result> Effect::setAudioSource(AudioSource source) {
|
||||
@@ -692,12 +689,11 @@ Return<void> Effect::getCurrentConfigForFeature(uint32_t featureId, uint32_t con
|
||||
|
||||
Return<Result> Effect::setCurrentConfigForFeature(uint32_t featureId,
|
||||
const hidl_vec<uint8_t>& configData) {
|
||||
uint32_t halCmd[alignedSizeIn<uint32_t>(sizeof(uint32_t) + configData.size())];
|
||||
memset(halCmd, 0, sizeof(halCmd));
|
||||
std::vector<uint32_t> halCmd(alignedSizeIn<uint32_t>(sizeof(uint32_t) + configData.size()), 0);
|
||||
halCmd[0] = featureId;
|
||||
memcpy(&halCmd[1], &configData[0], configData.size());
|
||||
return sendCommandReturningStatus(EFFECT_CMD_SET_FEATURE_CONFIG, "SET_FEATURE_CONFIG",
|
||||
sizeof(halCmd), halCmd);
|
||||
halCmd.size(), &halCmd[0]);
|
||||
}
|
||||
|
||||
Return<Result> Effect::close() {
|
||||
|
||||
@@ -129,6 +129,7 @@ package audio.policy.V1_0 {
|
||||
enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_BEACON;
|
||||
enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_BYPASS_INTERRUPTION_POLICY;
|
||||
enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_BYPASS_MUTE;
|
||||
enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_CAPTURE_PRIVATE;
|
||||
enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_DEEP_BUFFER;
|
||||
enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_HW_AV_SYNC;
|
||||
enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_HW_HOTWORD;
|
||||
@@ -212,6 +213,7 @@ package audio.policy.V1_0 {
|
||||
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
|
||||
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_ASSISTANCE_SONIFICATION;
|
||||
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_ASSISTANT;
|
||||
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_CALL_ASSISTANT;
|
||||
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_GAME;
|
||||
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_MEDIA;
|
||||
enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION;
|
||||
|
||||
@@ -342,6 +342,7 @@
|
||||
<xs:enumeration value="AUDIO_USAGE_GAME"/>
|
||||
<xs:enumeration value="AUDIO_USAGE_VIRTUAL_SOURCE"/>
|
||||
<xs:enumeration value="AUDIO_USAGE_ASSISTANT"/>
|
||||
<xs:enumeration value="AUDIO_USAGE_CALL_ASSISTANT"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
@@ -370,6 +371,7 @@
|
||||
<xs:enumeration value="AUDIO_FLAG_NO_MEDIA_PROJECTION"/>
|
||||
<xs:enumeration value="AUDIO_FLAG_MUTE_HAPTIC"/>
|
||||
<xs:enumeration value="AUDIO_FLAG_NO_SYSTEM_CAPTURE"/>
|
||||
<xs:enumeration value="AUDIO_FLAG_CAPTURE_PRIVATE"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
randolphs@google.com
|
||||
pirozzoj@google.com
|
||||
twasilczyk@google.com
|
||||
pfg@google.com
|
||||
gurunagarajan@google.com
|
||||
keunyoung@google.com
|
||||
|
||||
@@ -29,6 +29,11 @@ interface IAudioControl {
|
||||
*
|
||||
* For every context, a valid bus number (0 - num busses-1) must be returned. If an
|
||||
* unrecognized contextNumber is encountered, then -1 shall be returned.
|
||||
*
|
||||
* Deprecated: usage of this API and car_volume_groups.xml has been replaced with
|
||||
* car_audio_configuration.xml. If using car_audio_configuration.xml, then the framework
|
||||
* will not call this method. If it doesn't, then it will load car_volume_groups.xml and
|
||||
* call this method.
|
||||
*/
|
||||
getBusForContext(ContextNumber contextNumber)
|
||||
generates (int32_t busNumber);
|
||||
|
||||
@@ -29,10 +29,5 @@ cc_binary {
|
||||
"liblog",
|
||||
"libutils",
|
||||
],
|
||||
|
||||
cflags: [
|
||||
"-DLOG_TAG=\"AudCntrlDrv\"",
|
||||
"-O0",
|
||||
"-g",
|
||||
],
|
||||
vintf_fragments: ["audiocontrol_manifest.xml"],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<manifest version="1.0" type="device">
|
||||
<hal format="hidl">
|
||||
<name>android.hardware.automotive.audiocontrol</name>
|
||||
<transport>hwbinder</transport>
|
||||
<version>1.0</version>
|
||||
<interface>
|
||||
<name>IAudioControl</name>
|
||||
<instance>default</instance>
|
||||
</interface>
|
||||
</hal>
|
||||
</manifest>
|
||||
@@ -25,5 +25,8 @@ cc_test {
|
||||
static_libs: [
|
||||
"android.hardware.automotive.audiocontrol@1.0",
|
||||
],
|
||||
test_suites: ["general-tests"],
|
||||
test_suites: [
|
||||
"general-tests",
|
||||
"vts",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -25,11 +25,12 @@
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include <android/hardware/automotive/audiocontrol/1.0/types.h>
|
||||
#include <android/hardware/automotive/audiocontrol/1.0/IAudioControl.h>
|
||||
#include <android/hardware/automotive/audiocontrol/1.0/types.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <VtsHalHidlTargetTestBase.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <hidl/GtestPrinter.h>
|
||||
#include <hidl/ServiceManagement.h>
|
||||
|
||||
using namespace ::android::hardware::automotive::audiocontrol::V1_0;
|
||||
using ::android::hardware::Return;
|
||||
@@ -40,30 +41,12 @@ using ::android::hardware::hidl_string;
|
||||
using ::android::hardware::hidl_vec;
|
||||
using ::android::sp;
|
||||
|
||||
|
||||
// Boiler plate for test harness
|
||||
class CarAudioControlHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase {
|
||||
public:
|
||||
// get the test environment singleton
|
||||
static CarAudioControlHidlEnvironment* Instance() {
|
||||
static CarAudioControlHidlEnvironment* instance = new CarAudioControlHidlEnvironment;
|
||||
return instance;
|
||||
}
|
||||
|
||||
virtual void registerTestServices() override { registerTestService<IAudioControl>(); }
|
||||
private:
|
||||
CarAudioControlHidlEnvironment() {}
|
||||
};
|
||||
|
||||
|
||||
// The main test class for the automotive AudioControl HAL
|
||||
class CarAudioControlHidlTest : public ::testing::VtsHalHidlTargetTestBase {
|
||||
public:
|
||||
class CarAudioControlHidlTest : public ::testing::TestWithParam<std::string> {
|
||||
public:
|
||||
virtual void SetUp() override {
|
||||
// Make sure we can connect to the driver
|
||||
pAudioControl = ::testing::VtsHalHidlTargetTestBase::getService<IAudioControl>(
|
||||
CarAudioControlHidlEnvironment::Instance()->
|
||||
getServiceName<IAudioControl>());
|
||||
pAudioControl = IAudioControl::getService(GetParam());
|
||||
ASSERT_NE(pAudioControl.get(), nullptr);
|
||||
}
|
||||
|
||||
@@ -82,7 +65,7 @@ public:
|
||||
* fader actually works. The only thing we can do is exercise the HAL and if the HAL crashes,
|
||||
* we _might_ get a test failure if that breaks the connection to the driver.
|
||||
*/
|
||||
TEST_F(CarAudioControlHidlTest, FaderExercise) {
|
||||
TEST_P(CarAudioControlHidlTest, FaderExercise) {
|
||||
ALOGI("Fader exercise test (silent)");
|
||||
|
||||
// Set the fader all the way to the back
|
||||
@@ -104,7 +87,7 @@ TEST_F(CarAudioControlHidlTest, FaderExercise) {
|
||||
/*
|
||||
* Balance exercise test.
|
||||
*/
|
||||
TEST_F(CarAudioControlHidlTest, BalanceExercise) {
|
||||
TEST_P(CarAudioControlHidlTest, BalanceExercise) {
|
||||
ALOGI("Balance exercise test (silent)");
|
||||
|
||||
// Set the balance all the way to the left
|
||||
@@ -126,7 +109,7 @@ TEST_F(CarAudioControlHidlTest, BalanceExercise) {
|
||||
/*
|
||||
* Context mapping test.
|
||||
*/
|
||||
TEST_F(CarAudioControlHidlTest, ContextMapping) {
|
||||
TEST_P(CarAudioControlHidlTest, ContextMapping) {
|
||||
ALOGI("Context mapping test");
|
||||
|
||||
int bus = -1;
|
||||
@@ -156,3 +139,8 @@ TEST_F(CarAudioControlHidlTest, ContextMapping) {
|
||||
bus = pAudioControl->getBusForContext((ContextNumber)~0);
|
||||
EXPECT_EQ(bus, -1);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
PerInstance, CarAudioControlHidlTest,
|
||||
testing::ValuesIn(android::hardware::getAllHalInstanceNames(IAudioControl::descriptor)),
|
||||
android::hardware::PrintInstanceNameToString);
|
||||
18
automotive/audiocontrol/2.0/Android.bp
Normal file
18
automotive/audiocontrol/2.0/Android.bp
Normal file
@@ -0,0 +1,18 @@
|
||||
// This file is autogenerated by hidl-gen -Landroidbp.
|
||||
|
||||
hidl_interface {
|
||||
name: "android.hardware.automotive.audiocontrol@2.0",
|
||||
root: "android.hardware",
|
||||
srcs: [
|
||||
"types.hal",
|
||||
"IAudioControl.hal",
|
||||
"ICloseHandle.hal",
|
||||
"IFocusListener.hal",
|
||||
],
|
||||
interfaces: [
|
||||
"android.hardware.audio.common@6.0",
|
||||
"android.hidl.base@1.0",
|
||||
"android.hidl.safe_union@1.0",
|
||||
],
|
||||
gen_java: true,
|
||||
}
|
||||
78
automotive/audiocontrol/2.0/IAudioControl.hal
Normal file
78
automotive/audiocontrol/2.0/IAudioControl.hal
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.automotive.audiocontrol@2.0;
|
||||
|
||||
import ICloseHandle;
|
||||
import IFocusListener;
|
||||
import android.hardware.audio.common@6.0::AudioUsage;
|
||||
|
||||
/**
|
||||
* Interacts with the car's audio subsystem to manage audio sources and volumes
|
||||
*/
|
||||
interface IAudioControl {
|
||||
/**
|
||||
* Registers focus listener to be used by HAL for requesting and abandoning audio focus.
|
||||
*
|
||||
* It is expected that there will only ever be a single focus listener registered. If the
|
||||
* observer dies, the HAL implementation must unregister observer automatically. If called when
|
||||
* a listener is already registered, the existing one should be unregistered and replaced with
|
||||
* the new listener.
|
||||
*
|
||||
* @param listener the listener interface
|
||||
* @return closeHandle A handle to unregister observer.
|
||||
*/
|
||||
registerFocusListener(IFocusListener listener) generates (ICloseHandle closeHandle);
|
||||
|
||||
/**
|
||||
* Notifies HAL of changes in audio focus status for focuses requested or abandoned by the HAL.
|
||||
*
|
||||
* This will be called in response to IFocusListener's requestAudioFocus and
|
||||
* abandonAudioFocus, as well as part of any change in focus being held by the HAL due focus
|
||||
* request from other activities or services.
|
||||
*
|
||||
* The HAL is not required to wait for an callback of AUDIOFOCUS_GAIN before playing audio, nor
|
||||
* is it required to stop playing audio in the event of a AUDIOFOCUS_LOSS callback is received.
|
||||
*
|
||||
* @param usage The audio usage associated with the focus change {@code AttributeUsage}
|
||||
* @param zoneId The identifier for the audio zone that the HAL is playing the stream in
|
||||
* @param focusChange the AudioFocusChange that has occurred
|
||||
*/
|
||||
oneway onAudioFocusChange(bitfield<AudioUsage> usage, int32_t zoneId,
|
||||
bitfield<AudioFocusChange> focusChange);
|
||||
|
||||
/**
|
||||
* Control the right/left balance setting of the car speakers.
|
||||
*
|
||||
* This is intended to shift the speaker volume toward the right (+) or left (-) side of
|
||||
* the car. 0.0 means "centered". +1.0 means fully right. -1.0 means fully left.
|
||||
*
|
||||
* A value outside the range -1 to 1 must be clamped by the implementation to the -1 to 1
|
||||
* range.
|
||||
*/
|
||||
oneway setBalanceTowardRight(float value);
|
||||
|
||||
/**
|
||||
* Control the fore/aft fade setting of the car speakers.
|
||||
*
|
||||
* This is intended to shift the speaker volume toward the front (+) or back (-) of the car.
|
||||
* 0.0 means "centered". +1.0 means fully forward. -1.0 means fully rearward.
|
||||
*
|
||||
* A value outside the range -1 to 1 must be clamped by the implementation to the -1 to 1
|
||||
* range.
|
||||
*/
|
||||
oneway setFadeTowardFront(float value);
|
||||
};
|
||||
34
automotive/audiocontrol/2.0/ICloseHandle.hal
Normal file
34
automotive/audiocontrol/2.0/ICloseHandle.hal
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.automotive.audiocontrol@2.0;
|
||||
|
||||
/**
|
||||
* Represents a generic close handle to remove a callback that doesn't need
|
||||
* active interface.
|
||||
*
|
||||
* When close() is called OR when the interface is released, the underlying
|
||||
* resources must be freed.
|
||||
*/
|
||||
interface ICloseHandle {
|
||||
/**
|
||||
* Closes the handle.
|
||||
*
|
||||
* The call must not fail and must be issued by the client at most once.
|
||||
* Otherwise, the server must ignore subsequent calls.
|
||||
*/
|
||||
close();
|
||||
};
|
||||
55
automotive/audiocontrol/2.0/IFocusListener.hal
Normal file
55
automotive/audiocontrol/2.0/IFocusListener.hal
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.automotive.audiocontrol@2.0;
|
||||
|
||||
import android.hardware.audio.common@6.0::AudioUsage;
|
||||
|
||||
/**
|
||||
* Callback interface for audio focus listener.
|
||||
*
|
||||
* For typical configuration, the listener the car audio service.
|
||||
*/
|
||||
interface IFocusListener {
|
||||
/**
|
||||
* Called whenever HAL is requesting focus as it is starting to play audio of a given usage in a
|
||||
* specified zone.
|
||||
*
|
||||
* In response, IAudioControl#onAudioFocusChange will be called with focusChange status. This
|
||||
* interaction is oneway to avoid blocking HAL so that it is not required to wait for a response
|
||||
* before playing audio.
|
||||
*
|
||||
* @param usage The audio usage associated with the focus request {@code AttributeUsage}
|
||||
* @param zoneId The identifier for the audio zone where the HAL is requesting focus
|
||||
* @param focusGain The AudioFocusChange associated with this request. Should be one of the
|
||||
* following: GAIN, GAIN_TRANSIENT, GAIN_TRANSIENT_MAY_DUCK, GAIN_TRANSIENT_EXCLUSIVE.
|
||||
*/
|
||||
oneway requestAudioFocus(bitfield<AudioUsage> usage, int32_t zoneId,
|
||||
bitfield<AudioFocusChange> focusGain);
|
||||
|
||||
/**
|
||||
* Called whenever HAL is abandoning focus as it is finished playing audio of a given usage in a
|
||||
* specific zone.
|
||||
*
|
||||
* In response, IAudioControl#onAudioFocusChange will be called with focusChange status. This
|
||||
* interaction is oneway to avoid blocking HAL so that it is not required to wait for a response
|
||||
* before stopping audio playback.
|
||||
*
|
||||
* @param usage The audio usage for which the HAL is abandoning focus {@code AttributeUsage}
|
||||
* @param zoneId The identifier for the audio zone that the HAL abandoning focus
|
||||
*/
|
||||
oneway abandonAudioFocus(bitfield<AudioUsage> usage, int32_t zoneId);
|
||||
};
|
||||
39
automotive/audiocontrol/2.0/default/Android.bp
Normal file
39
automotive/audiocontrol/2.0/default/Android.bp
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2020 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
cc_binary {
|
||||
name: "android.hardware.automotive.audiocontrol@2.0-service",
|
||||
defaults: ["hidl_defaults"],
|
||||
vendor: true,
|
||||
relative_install_path: "hw",
|
||||
srcs: [
|
||||
"AudioControl.cpp",
|
||||
"service.cpp",
|
||||
"CloseHandle.cpp",
|
||||
],
|
||||
init_rc: ["android.hardware.automotive.audiocontrol@2.0-service.rc"],
|
||||
|
||||
shared_libs: [
|
||||
"android.hardware.automotive.audiocontrol@2.0",
|
||||
"libbase",
|
||||
"libhidlbase",
|
||||
"liblog",
|
||||
"libutils",
|
||||
],
|
||||
vintf_fragments: ["audiocontrol2_manifest.xml"],
|
||||
cflags: [
|
||||
"-O0",
|
||||
"-g",
|
||||
],
|
||||
}
|
||||
220
automotive/audiocontrol/2.0/default/AudioControl.cpp
Normal file
220
automotive/audiocontrol/2.0/default/AudioControl.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AudioControl.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/strings.h>
|
||||
|
||||
#include <hidl/HidlTransportSupport.h>
|
||||
#include <hwbinder/IPCThreadState.h>
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include "CloseHandle.h"
|
||||
|
||||
namespace android::hardware::automotive::audiocontrol::V2_0::implementation {
|
||||
|
||||
using ::android::base::EqualsIgnoreCase;
|
||||
using ::android::hardware::hidl_handle;
|
||||
using ::android::hardware::hidl_string;
|
||||
|
||||
static const float kLowerBound = -1.0f;
|
||||
static const float kUpperBound = 1.0f;
|
||||
|
||||
AudioControl::AudioControl() {}
|
||||
|
||||
Return<sp<ICloseHandle>> AudioControl::registerFocusListener(const sp<IFocusListener>& listener) {
|
||||
LOG(DEBUG) << "registering focus listener";
|
||||
sp<ICloseHandle> closeHandle(nullptr);
|
||||
|
||||
if (listener) {
|
||||
mFocusListener = listener;
|
||||
|
||||
closeHandle = new CloseHandle([this, listener]() {
|
||||
if (mFocusListener == listener) {
|
||||
mFocusListener = nullptr;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG(ERROR) << "Unexpected nullptr for listener resulting in no-op.";
|
||||
}
|
||||
|
||||
return closeHandle;
|
||||
}
|
||||
|
||||
Return<void> AudioControl::setBalanceTowardRight(float value) {
|
||||
if (isValidValue(value)) {
|
||||
// Just log in this default mock implementation
|
||||
LOG(INFO) << "Balance set to " << value;
|
||||
} else {
|
||||
LOG(ERROR) << "Balance value out of range -1 to 1 at " << value;
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<void> AudioControl::setFadeTowardFront(float value) {
|
||||
if (isValidValue(value)) {
|
||||
// Just log in this default mock implementation
|
||||
LOG(INFO) << "Fader set to " << value;
|
||||
} else {
|
||||
LOG(ERROR) << "Fader value out of range -1 to 1 at " << value;
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
bool AudioControl::isValidValue(float value) {
|
||||
return (value > kLowerBound) && (value < kUpperBound);
|
||||
}
|
||||
|
||||
Return<void> AudioControl::onAudioFocusChange(hidl_bitfield<AudioUsage> usage, int zoneId,
|
||||
hidl_bitfield<AudioFocusChange> focusChange) {
|
||||
LOG(INFO) << "Focus changed: " << static_cast<int>(focusChange) << " for usage "
|
||||
<< static_cast<int>(usage) << " in zone " << zoneId;
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<void> AudioControl::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) {
|
||||
if (fd.getNativeHandle() == nullptr || fd->numFds == 0) {
|
||||
LOG(ERROR) << "Invalid parameters passed to debug()";
|
||||
return Void();
|
||||
}
|
||||
|
||||
cmdDump(fd->data[0], options);
|
||||
return Void();
|
||||
}
|
||||
|
||||
void AudioControl::cmdDump(int fd, const hidl_vec<hidl_string>& options) {
|
||||
if (options.size() == 0) {
|
||||
dump(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string option = options[0];
|
||||
if (EqualsIgnoreCase(option, "--help")) {
|
||||
cmdHelp(fd);
|
||||
} else if (EqualsIgnoreCase(option, "--request")) {
|
||||
cmdRequestFocus(fd, options);
|
||||
} else if (EqualsIgnoreCase(option, "--abandon")) {
|
||||
cmdAbandonFocus(fd, options);
|
||||
} else {
|
||||
dprintf(fd, "Invalid option: %s\n", option.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void AudioControl::dump(int fd) {
|
||||
if (mFocusListener == nullptr) {
|
||||
dprintf(fd, "No focus listener registered\n");
|
||||
} else {
|
||||
dprintf(fd, "Focus listener registered\n");
|
||||
}
|
||||
}
|
||||
|
||||
void AudioControl::cmdHelp(int fd) const {
|
||||
dprintf(fd, "Usage: \n\n");
|
||||
dprintf(fd, "[no args]: dumps focus listener status\n");
|
||||
dprintf(fd, "--help: shows this help\n");
|
||||
dprintf(fd,
|
||||
"--request <USAGE> <ZONE_ID> <FOCUS_GAIN>: requests audio focus for specified "
|
||||
"usage (int), audio zone ID (int), and focus gain type (int)\n");
|
||||
dprintf(fd,
|
||||
"--abandon <USAGE> <ZONE_ID>: abandons audio focus for specified usage (int) and "
|
||||
"audio zone ID (int)\n");
|
||||
}
|
||||
|
||||
void AudioControl::cmdRequestFocus(int fd, const hidl_vec<hidl_string>& options) {
|
||||
if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 3)) return;
|
||||
|
||||
hidl_bitfield<AudioUsage> usage;
|
||||
if (!safelyParseInt(options[1], &usage)) {
|
||||
dprintf(fd, "Non-integer usage provided with request: %s\n", options[1].c_str());
|
||||
return;
|
||||
}
|
||||
int zoneId;
|
||||
if (!safelyParseInt(options[2], &zoneId)) {
|
||||
dprintf(fd, "Non-integer zoneId provided with request: %s\n", options[2].c_str());
|
||||
return;
|
||||
}
|
||||
hidl_bitfield<AudioFocusChange> focusGain;
|
||||
if (!safelyParseInt(options[3], &focusGain)) {
|
||||
dprintf(fd, "Non-integer focusGain provided with request: %s\n", options[3].c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFocusListener == nullptr) {
|
||||
dprintf(fd, "Unable to request focus - no focus listener registered\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mFocusListener->requestAudioFocus(usage, zoneId, focusGain);
|
||||
dprintf(fd, "Requested focus for usage %d, zoneId %d, and focusGain %d\n", usage, zoneId,
|
||||
focusGain);
|
||||
}
|
||||
|
||||
void AudioControl::cmdAbandonFocus(int fd, const hidl_vec<hidl_string>& options) {
|
||||
if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 2)) return;
|
||||
|
||||
hidl_bitfield<AudioUsage> usage;
|
||||
if (!safelyParseInt(options[1], &usage)) {
|
||||
dprintf(fd, "Non-integer usage provided with abandon: %s\n", options[1].c_str());
|
||||
return;
|
||||
}
|
||||
int zoneId;
|
||||
if (!safelyParseInt(options[2], &zoneId)) {
|
||||
dprintf(fd, "Non-integer zoneId provided with abandon: %s\n", options[2].c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFocusListener == nullptr) {
|
||||
dprintf(fd, "Unable to abandon focus - no focus listener registered\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mFocusListener->abandonAudioFocus(usage, zoneId);
|
||||
dprintf(fd, "Abandoned focus for usage %d and zoneId %d\n", usage, zoneId);
|
||||
}
|
||||
|
||||
bool AudioControl::checkCallerHasWritePermissions(int fd) {
|
||||
// Double check that's only called by root - it should be be blocked at the HIDL debug() level,
|
||||
// but it doesn't hurt to make sure...
|
||||
if (hardware::IPCThreadState::self()->getCallingUid() != AID_ROOT) {
|
||||
dprintf(fd, "Must be root\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioControl::checkArgumentsSize(int fd, const hidl_vec<hidl_string>& options,
|
||||
size_t expectedSize) {
|
||||
// options includes the command, so reducing size by one
|
||||
size_t size = options.size() - 1;
|
||||
if (size == expectedSize) {
|
||||
return true;
|
||||
}
|
||||
dprintf(fd, "Invalid number of arguments: required %zu, got %zu\n", expectedSize, size);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioControl::safelyParseInt(std::string s, int* out) {
|
||||
if (!android::base::ParseInt(s, out)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation
|
||||
60
automotive/audiocontrol/2.0/default/AudioControl.h
Normal file
60
automotive/audiocontrol/2.0/default/AudioControl.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ANDROID_HARDWARE_AUTOMOTIVE_AUDIOCONTROL_V2_0_AUDIOCONTROL_H
|
||||
#define ANDROID_HARDWARE_AUTOMOTIVE_AUDIOCONTROL_V2_0_AUDIOCONTROL_H
|
||||
|
||||
#include <android/hardware/audio/common/6.0/types.h>
|
||||
#include <android/hardware/automotive/audiocontrol/2.0/IAudioControl.h>
|
||||
#include <android/hardware/automotive/audiocontrol/2.0/ICloseHandle.h>
|
||||
#include <hidl/MQDescriptor.h>
|
||||
#include <hidl/Status.h>
|
||||
|
||||
using android::hardware::audio::common::V6_0::AudioUsage;
|
||||
|
||||
namespace android::hardware::automotive::audiocontrol::V2_0::implementation {
|
||||
|
||||
class AudioControl : public IAudioControl {
|
||||
public:
|
||||
// Methods from ::android::hardware::automotive::audiocontrol::V2_0::IAudioControl follow.
|
||||
Return<sp<ICloseHandle>> registerFocusListener(const sp<IFocusListener>& listener);
|
||||
Return<void> onAudioFocusChange(hidl_bitfield<AudioUsage> usage, int zoneId,
|
||||
hidl_bitfield<AudioFocusChange> focusChange);
|
||||
Return<void> setBalanceTowardRight(float value) override;
|
||||
Return<void> setFadeTowardFront(float value) override;
|
||||
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
|
||||
|
||||
// Implementation details
|
||||
AudioControl();
|
||||
|
||||
private:
|
||||
sp<IFocusListener> mFocusListener;
|
||||
|
||||
static bool checkArgumentsSize(int fd, const hidl_vec<hidl_string>& options, size_t minSize);
|
||||
static bool checkCallerHasWritePermissions(int fd);
|
||||
static bool isValidValue(float value);
|
||||
static bool safelyParseInt(std::string s, int* out);
|
||||
|
||||
void cmdDump(int fd, const hidl_vec<hidl_string>& options);
|
||||
void cmdHelp(int fd) const;
|
||||
void cmdRequestFocus(int fd, const hidl_vec<hidl_string>& options);
|
||||
void cmdAbandonFocus(int fd, const hidl_vec<hidl_string>& options);
|
||||
void dump(int fd);
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation
|
||||
|
||||
#endif // ANDROID_HARDWARE_AUTOMOTIVE_AUDIOCONTROL_V2_0_AUDIOCONTROL_H
|
||||
35
automotive/audiocontrol/2.0/default/CloseHandle.cpp
Normal file
35
automotive/audiocontrol/2.0/default/CloseHandle.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CloseHandle.h"
|
||||
|
||||
namespace android::hardware::automotive::audiocontrol::V2_0::implementation {
|
||||
|
||||
CloseHandle::CloseHandle(Callback callback) : mCallback(callback) {}
|
||||
|
||||
CloseHandle::~CloseHandle() {
|
||||
close();
|
||||
}
|
||||
|
||||
Return<void> CloseHandle::close() {
|
||||
const auto wasClosed = mIsClosed.exchange(true);
|
||||
if (wasClosed) return {};
|
||||
|
||||
if (mCallback) mCallback();
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation
|
||||
48
automotive/audiocontrol/2.0/default/CloseHandle.h
Normal file
48
automotive/audiocontrol/2.0/default/CloseHandle.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android/hardware/automotive/audiocontrol/2.0/ICloseHandle.h>
|
||||
|
||||
namespace android::hardware::automotive::audiocontrol::V2_0::implementation {
|
||||
|
||||
/** Generic ICloseHandle implementation ignoring double-close events. */
|
||||
class CloseHandle : public ICloseHandle {
|
||||
public:
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
/**
|
||||
* Create a handle with a callback.
|
||||
*
|
||||
* The callback is guaranteed to be called exactly once.
|
||||
*
|
||||
* \param callback Called on the first close() call, or on destruction of the handle
|
||||
*/
|
||||
CloseHandle(Callback callback = nullptr);
|
||||
virtual ~CloseHandle();
|
||||
|
||||
Return<void> close() override;
|
||||
|
||||
private:
|
||||
const Callback mCallback;
|
||||
std::atomic<bool> mIsClosed = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CloseHandle);
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation
|
||||
@@ -0,0 +1,4 @@
|
||||
service vendor.audiocontrol-hal-2.0 /vendor/bin/hw/android.hardware.automotive.audiocontrol@2.0-service
|
||||
class hal
|
||||
user audioserver
|
||||
group system
|
||||
@@ -0,0 +1,11 @@
|
||||
<manifest version="1.0" type="device">
|
||||
<hal format="hidl">
|
||||
<name>android.hardware.automotive.audiocontrol</name>
|
||||
<transport>hwbinder</transport>
|
||||
<version>2.0</version>
|
||||
<interface>
|
||||
<name>IAudioControl</name>
|
||||
<instance>default</instance>
|
||||
</interface>
|
||||
</hal>
|
||||
</manifest>
|
||||
52
automotive/audiocontrol/2.0/default/service.cpp
Normal file
52
automotive/audiocontrol/2.0/default/service.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <hidl/HidlTransportSupport.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include "AudioControl.h"
|
||||
|
||||
// libhidl:
|
||||
using android::hardware::configureRpcThreadpool;
|
||||
using android::hardware::joinRpcThreadpool;
|
||||
|
||||
// Generated HIDL files
|
||||
using android::hardware::automotive::audiocontrol::V2_0::IAudioControl;
|
||||
|
||||
// The namespace in which all our implementation code lives
|
||||
using namespace android::hardware::automotive::audiocontrol::V2_0::implementation;
|
||||
using namespace android;
|
||||
|
||||
// Main service entry point
|
||||
int main() {
|
||||
// Create an instance of our service class
|
||||
android::sp<IAudioControl> service = new AudioControl();
|
||||
configureRpcThreadpool(1, true /*callerWillJoin*/);
|
||||
|
||||
if (service->registerAsService() != OK) {
|
||||
LOG(ERROR) << "registerAsService failed";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Join (forever) the thread pool we created for the service above
|
||||
joinRpcThreadpool();
|
||||
|
||||
// We don't ever actually expect to return, so return an error if we do get here
|
||||
return 2;
|
||||
}
|
||||
31
automotive/audiocontrol/2.0/types.hal
Normal file
31
automotive/audiocontrol/2.0/types.hal
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.automotive.audiocontrol@2.0;
|
||||
|
||||
/**
|
||||
* Changes in audio focus that can be experienced
|
||||
*/
|
||||
enum AudioFocusChange : int32_t {
|
||||
NONE = 0,
|
||||
GAIN = 1,
|
||||
GAIN_TRANSIENT = 2,
|
||||
GAIN_TRANSIENT_MAY_DUCK = 3,
|
||||
GAIN_TRANSIENT_EXCLUSIVE = 4,
|
||||
LOSS = -1 * GAIN,
|
||||
LOSS_TRANSIENT = -1 * GAIN_TRANSIENT,
|
||||
LOSS_TRANSIENT_CAN_DUCK = -1 * GAIN_TRANSIENT_MAY_DUCK,
|
||||
};
|
||||
29
automotive/audiocontrol/2.0/vts/functional/Android.bp
Normal file
29
automotive/audiocontrol/2.0/vts/functional/Android.bp
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright (C) 2020 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
cc_test {
|
||||
name: "VtsHalAudioControlV2_0TargetTest",
|
||||
defaults: ["VtsHalTargetTestDefaults"],
|
||||
srcs: ["VtsHalAudioControlV2_0TargetTest.cpp"],
|
||||
static_libs: [
|
||||
"android.hardware.automotive.audiocontrol@2.0",
|
||||
"libgmock",
|
||||
],
|
||||
test_suites: [
|
||||
"general-tests",
|
||||
"vts-core",
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "VtsHalAudioControlTest"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <hidl/GtestPrinter.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <hidl/HidlTransportSupport.h>
|
||||
#include <hwbinder/ProcessState.h>
|
||||
#include <log/log.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include <android/hardware/audio/common/6.0/types.h>
|
||||
#include <android/hardware/automotive/audiocontrol/2.0/IAudioControl.h>
|
||||
#include <android/hardware/automotive/audiocontrol/2.0/types.h>
|
||||
#include <android/log.h>
|
||||
|
||||
using namespace ::android::hardware::automotive::audiocontrol::V2_0;
|
||||
using ::android::sp;
|
||||
using ::android::hardware::hidl_bitfield;
|
||||
using ::android::hardware::hidl_enum_range;
|
||||
using ::android::hardware::hidl_handle;
|
||||
using ::android::hardware::hidl_string;
|
||||
using ::android::hardware::hidl_vec;
|
||||
using ::android::hardware::Return;
|
||||
using ::android::hardware::Void;
|
||||
using ::android::hardware::audio::common::V6_0::AudioUsage;
|
||||
|
||||
// The main test class for the automotive AudioControl HAL
|
||||
class CarAudioControlHidlTest : public testing::TestWithParam<std::string> {
|
||||
public:
|
||||
virtual void SetUp() override {
|
||||
// Make sure we can connect to the driver
|
||||
pAudioControl = IAudioControl::getService(GetParam());
|
||||
ASSERT_NE(pAudioControl.get(), nullptr);
|
||||
}
|
||||
|
||||
virtual void TearDown() override {}
|
||||
|
||||
protected:
|
||||
sp<IAudioControl> pAudioControl; // Every test needs access to the service
|
||||
};
|
||||
|
||||
//
|
||||
// Tests start here...
|
||||
//
|
||||
|
||||
/*
|
||||
* Fader exercise test. Note that only a subjective observer could determine if the
|
||||
* fader actually works. The only thing we can do is exercise the HAL and if the HAL crashes,
|
||||
* we _might_ get a test failure if that breaks the connection to the driver.
|
||||
*/
|
||||
TEST_P(CarAudioControlHidlTest, FaderExercise) {
|
||||
ALOGI("Fader exercise test (silent)");
|
||||
|
||||
// Set the fader all the way to the back
|
||||
pAudioControl->setFadeTowardFront(-1.0f);
|
||||
|
||||
// Set the fader all the way to the front
|
||||
pAudioControl->setFadeTowardFront(1.0f);
|
||||
|
||||
// Set the fader part way toward the back
|
||||
pAudioControl->setFadeTowardFront(-0.333f);
|
||||
|
||||
// Set the fader to a out of bounds value (driver should clamp)
|
||||
pAudioControl->setFadeTowardFront(99999.9f);
|
||||
|
||||
// Set the fader back to the middle
|
||||
pAudioControl->setFadeTowardFront(0.0f);
|
||||
}
|
||||
|
||||
/*
|
||||
* Balance exercise test.
|
||||
*/
|
||||
TEST_P(CarAudioControlHidlTest, BalanceExercise) {
|
||||
ALOGI("Balance exercise test (silent)");
|
||||
|
||||
// Set the balance all the way to the left
|
||||
pAudioControl->setBalanceTowardRight(-1.0f);
|
||||
|
||||
// Set the balance all the way to the right
|
||||
pAudioControl->setBalanceTowardRight(1.0f);
|
||||
|
||||
// Set the balance part way toward the left
|
||||
pAudioControl->setBalanceTowardRight(-0.333f);
|
||||
|
||||
// Set the balance to a out of bounds value (driver should clamp)
|
||||
pAudioControl->setBalanceTowardRight(99999.9f);
|
||||
|
||||
// Set the balance back to the middle
|
||||
pAudioControl->setBalanceTowardRight(0.0f);
|
||||
}
|
||||
|
||||
struct FocusListenerMock : public IFocusListener {
|
||||
MOCK_METHOD(Return<void>, requestAudioFocus,
|
||||
(hidl_bitfield<AudioUsage> usage, int zoneId,
|
||||
hidl_bitfield<AudioFocusChange> focusGain));
|
||||
MOCK_METHOD(Return<void>, abandonAudioFocus, (hidl_bitfield<AudioUsage> usage, int zoneId));
|
||||
};
|
||||
|
||||
/*
|
||||
* Test focus listener registration.
|
||||
*
|
||||
* Verifies that:
|
||||
* - registerFocusListener succeeds;
|
||||
* - registering a second listener succeeds in replacing the first;
|
||||
* - closing handle does not crash;
|
||||
*/
|
||||
TEST_P(CarAudioControlHidlTest, FocusListenerRegistration) {
|
||||
ALOGI("Focus listener test");
|
||||
|
||||
sp<FocusListenerMock> listener = new FocusListenerMock();
|
||||
|
||||
auto hidlResult = pAudioControl->registerFocusListener(listener);
|
||||
ASSERT_TRUE(hidlResult.isOk());
|
||||
|
||||
sp<FocusListenerMock> listener2 = new FocusListenerMock();
|
||||
|
||||
auto hidlResult2 = pAudioControl->registerFocusListener(listener2);
|
||||
ASSERT_TRUE(hidlResult2.isOk());
|
||||
|
||||
const sp<ICloseHandle>& closeHandle = hidlResult2;
|
||||
closeHandle->close();
|
||||
};
|
||||
|
||||
TEST_P(CarAudioControlHidlTest, FocusChangeExercise) {
|
||||
ALOGI("Focus Change test");
|
||||
|
||||
pAudioControl->onAudioFocusChange(AudioUsage::MEDIA | 0, 0,
|
||||
AudioFocusChange::GAIN_TRANSIENT | 0);
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
PerInstance, CarAudioControlHidlTest,
|
||||
testing::ValuesIn(android::hardware::getAllHalInstanceNames(IAudioControl::descriptor)),
|
||||
android::hardware::PrintInstanceNameToString);
|
||||
18
automotive/can/1.0/Android.bp
Normal file
18
automotive/can/1.0/Android.bp
Normal file
@@ -0,0 +1,18 @@
|
||||
// This file is autogenerated by hidl-gen -Landroidbp.
|
||||
|
||||
hidl_interface {
|
||||
name: "android.hardware.automotive.can@1.0",
|
||||
root: "android.hardware",
|
||||
srcs: [
|
||||
"types.hal",
|
||||
"ICanBus.hal",
|
||||
"ICanController.hal",
|
||||
"ICanErrorListener.hal",
|
||||
"ICanMessageListener.hal",
|
||||
"ICloseHandle.hal",
|
||||
],
|
||||
interfaces: [
|
||||
"android.hidl.base@1.0",
|
||||
],
|
||||
gen_java: true,
|
||||
}
|
||||
72
automotive/can/1.0/ICanBus.hal
Normal file
72
automotive/can/1.0/ICanBus.hal
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.hardware.automotive.can@1.0;
|
||||
|
||||
import ICanErrorListener;
|
||||
import ICanMessageListener;
|
||||
import ICloseHandle;
|
||||
|
||||
/**
|
||||
* Represents a CAN bus interface that's up and configured.
|
||||
*
|
||||
* Configuration part is done in ICanController.
|
||||
*/
|
||||
interface ICanBus {
|
||||
/**
|
||||
* Send CAN message.
|
||||
*
|
||||
* @param message CAN message to send out
|
||||
* @return result OK in the case of success
|
||||
* PAYLOAD_TOO_LONG if the payload is too long
|
||||
* INTERFACE_DOWN if the bus is down
|
||||
* TRANSMISSION_FAILURE in case of transmission failure
|
||||
*/
|
||||
send(CanMessage message) generates (Result result);
|
||||
|
||||
/**
|
||||
* Requests HAL implementation to listen for specific CAN messages.
|
||||
*
|
||||
* HAL is responsible for maintaining listener set and sending out messages
|
||||
* to each listener that matches given filter against received message.
|
||||
*
|
||||
* Empty filter list means no filtering. If two or more listeners requested
|
||||
* different filters, HAL server must merge these to fulfill the superset of
|
||||
* these filters. HAL must not send out a message to a listener which filter
|
||||
* doesn't match given message id.
|
||||
*
|
||||
* If filtering is not supported at the hardware level (what's strongly
|
||||
* recommended), it must be covered in the HAL.
|
||||
*
|
||||
* @param filter The set of requested filters
|
||||
* @param listener The interface to receive the messages on
|
||||
* @return result OK in the case of success
|
||||
* INTERFACE_DOWN if the bus is down
|
||||
* @return close A handle to call in order to remove the listener
|
||||
*/
|
||||
listen(vec<CanMessageFilter> filter, ICanMessageListener listener)
|
||||
generates (Result result, ICloseHandle close);
|
||||
|
||||
/**
|
||||
* Adds a new listener for CAN bus or interface errors.
|
||||
*
|
||||
* If the error is fatal, the client is supposed to drop any references to
|
||||
* this specific ICanBus instance (see ICanErrorListener).
|
||||
*
|
||||
* @param listener The interface to receive the error events on
|
||||
* @return close A handle to call in order to remove the listener
|
||||
*/
|
||||
listenForErrors(ICanErrorListener listener) generates (ICloseHandle close);
|
||||
};
|
||||
195
automotive/can/1.0/ICanController.hal
Normal file
195
automotive/can/1.0/ICanController.hal
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.hardware.automotive.can@1.0;
|
||||
|
||||
/**
|
||||
* Represents a CAN controller that's capable of configuring CAN bus interfaces.
|
||||
*
|
||||
* The goal of this service is to configure CAN interfaces and bring up HIDL
|
||||
* server instances of ICanBus for each one that's up.
|
||||
*
|
||||
* Providing an ICanController interface to configure CAN buses is optional.
|
||||
* A system can elect to publish only ICanBus if the hardware is hardcoded
|
||||
* for a specific application.
|
||||
*/
|
||||
interface ICanController {
|
||||
/**
|
||||
* Type of an interface, an equivalent to BusConfig::InterfaceId
|
||||
* union discriminator. Defines a number of specific standard hardware
|
||||
* families and a generic catch-all type of {@see INDEXED}.
|
||||
*/
|
||||
enum InterfaceType : uint8_t {
|
||||
/** Virtual SocketCAN interface. */
|
||||
VIRTUAL,
|
||||
|
||||
/** Native SocketCAN interface. */
|
||||
SOCKETCAN,
|
||||
|
||||
/** Serial line CAN interface. */
|
||||
SLCAN,
|
||||
|
||||
/** Proprietary, device-specific interface. */
|
||||
INDEXED,
|
||||
};
|
||||
|
||||
enum Result : uint8_t {
|
||||
OK,
|
||||
|
||||
/**
|
||||
* General error class, if others are not applicable.
|
||||
*/
|
||||
UNKNOWN_ERROR,
|
||||
|
||||
/**
|
||||
* Up request was called out of order (i.e. trying to up the
|
||||
* interface twice).
|
||||
*/
|
||||
INVALID_STATE,
|
||||
|
||||
/** Interface type is not supported. */
|
||||
NOT_SUPPORTED,
|
||||
|
||||
/**
|
||||
* Provided interface ID (index, name, device path) doesn't exist or
|
||||
* there is no device with a given serial number.
|
||||
*/
|
||||
BAD_INTERFACE_ID,
|
||||
|
||||
/** Provided bit rate is not supported by the hardware. */
|
||||
BAD_BITRATE,
|
||||
|
||||
/**
|
||||
* Provided service name ({@see BusConfig#name}) either has invalid
|
||||
* format or is not listed in device manifest file.
|
||||
*/
|
||||
BAD_SERVICE_NAME,
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration of the (physical or virtual) CAN bus.
|
||||
*
|
||||
* ISO TP and CAN FD are currently not supported.
|
||||
*/
|
||||
struct BusConfig {
|
||||
/**
|
||||
* Name under which ICanBus HIDL service should be published.
|
||||
*
|
||||
* It must consist of only alphanumeric characters and underscore
|
||||
* (a-z, A-Z, 0-9, '_'), at least 1 and at most 32 characters long.
|
||||
*
|
||||
* This field is *not* meant to distinguish between hardware interfaces
|
||||
* nor preselect parameters like bitrate. The only intended side-effect
|
||||
* of changing it should be a different ICanBus HIDL service name and
|
||||
* the HIDL service should make no assumptions on its contents.
|
||||
*/
|
||||
string name;
|
||||
|
||||
/**
|
||||
* Hardware interface configuration.
|
||||
*
|
||||
* This union's discriminator has an equivalent enum
|
||||
* {@see InterfaceType} to express compatibility via
|
||||
* getSupportedInterfaceTypes().
|
||||
*/
|
||||
safe_union InterfaceId {
|
||||
/** Virtual SocketCAN interface. */
|
||||
struct Virtual {
|
||||
/** Interface name, such as vcan0. If the interface doesn't
|
||||
* exist, HAL server must create it.
|
||||
*/
|
||||
string ifname;
|
||||
} virtualif;
|
||||
|
||||
/** Native SocketCAN interface. */
|
||||
safe_union Socketcan {
|
||||
/** Interface name, such as can0. */
|
||||
string ifname;
|
||||
/**
|
||||
* Alternatively to providing {@see ifname}, one may provide a
|
||||
* list of interface serial number suffixes. If there happens to
|
||||
* be a device (like USB2CAN) with a matching serial number
|
||||
* suffix, the HAL service will have to select it.
|
||||
*
|
||||
* Client may utilize this in two ways: by matching against the
|
||||
* entire serial number, or the last few characters (usually
|
||||
* one). The former is better for small-scale test deployments
|
||||
* (with just a handful of vehicles), the latter is good for
|
||||
* larger scale (where a small suffix list may support large
|
||||
* test fleet).
|
||||
*/
|
||||
vec<string> serialno;
|
||||
} socketcan;
|
||||
|
||||
/** Serial line CAN interface. */
|
||||
safe_union Slcan {
|
||||
/** Path to a device, such as /dev/ttyUSB0. */
|
||||
string ttyname;
|
||||
/**
|
||||
* List of interface serial number suffixes.
|
||||
* {@see Socketcan::serialno}
|
||||
*/
|
||||
vec<string> serialno;
|
||||
} slcan;
|
||||
|
||||
/**
|
||||
* Proprietary, device-specific interface.
|
||||
*
|
||||
* Non-SocketCAN interfaces should use this variant.
|
||||
*/
|
||||
struct Indexed {
|
||||
/** Interface number, 0-based. */
|
||||
uint8_t index;
|
||||
} indexed;
|
||||
} interfaceId;
|
||||
|
||||
/**
|
||||
* Bit rate for CAN communication.
|
||||
*
|
||||
* Typical bit rates are: 100000, 125000, 250000, 500000.
|
||||
*
|
||||
* For {@see interfaceId#virtual} and pre-configured
|
||||
* {@see interfaceId#indexed} interfaces this value is ignored.
|
||||
*/
|
||||
uint32_t bitrate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the list of interface types supported by this HAL server.
|
||||
*
|
||||
* @return iftypes The list of supported interface types.
|
||||
*/
|
||||
getSupportedInterfaceTypes() generates (vec<InterfaceType> iftypes);
|
||||
|
||||
/**
|
||||
* Bring up the CAN interface and publish ICanBus server instance.
|
||||
*
|
||||
* @param config Configuration of the CAN interface.
|
||||
* @return result OK if the operation succeeded; error code otherwise.
|
||||
*/
|
||||
upInterface(BusConfig config) generates (Result result);
|
||||
|
||||
/**
|
||||
* Unpublish ICanBus server instance and bring down the CAN interface.
|
||||
*
|
||||
* In case of failure, at least the ICanBus server instance must be
|
||||
* unpublished and resources freed on best-effort basis.
|
||||
*
|
||||
* @param name Name of the interface (@see BusConfig#name} to
|
||||
* bring down.
|
||||
* @return success true in case of success, false otherwise.
|
||||
*/
|
||||
downInterface(string name) generates (bool success);
|
||||
};
|
||||
32
automotive/can/1.0/ICanErrorListener.hal
Normal file
32
automotive/can/1.0/ICanErrorListener.hal
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.hardware.automotive.can@1.0;
|
||||
|
||||
/**
|
||||
* CAN error listener.
|
||||
*/
|
||||
interface ICanErrorListener {
|
||||
/**
|
||||
* Called on error event.
|
||||
*
|
||||
* If the error is fatal, the client is supposed to drop any references to
|
||||
* this specific ICanBus instance.
|
||||
*
|
||||
* @param error Error type
|
||||
* @param isFatal Whether an error would result with ICanBus instance being unusable.
|
||||
*/
|
||||
onError(ErrorEvent error, bool isFatal);
|
||||
};
|
||||
33
automotive/can/1.0/ICanMessageListener.hal
Normal file
33
automotive/can/1.0/ICanMessageListener.hal
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.hardware.automotive.can@1.0;
|
||||
|
||||
/**
|
||||
* CAN message listener.
|
||||
*/
|
||||
interface ICanMessageListener {
|
||||
/**
|
||||
* Called on received CAN message.
|
||||
*
|
||||
* The timestamp field of message struct is set to time when the message
|
||||
* was received by the hardware. If it's not possible to fetch exact
|
||||
* hardware time, this field should be set as early as possible to decrease
|
||||
* potential time delta.
|
||||
*
|
||||
* @param message Received CAN message
|
||||
*/
|
||||
onReceive(CanMessage message);
|
||||
};
|
||||
33
automotive/can/1.0/ICloseHandle.hal
Normal file
33
automotive/can/1.0/ICloseHandle.hal
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.hardware.automotive.can@1.0;
|
||||
|
||||
/**
|
||||
* Represents a generic close handle to remove a callback that doesn't need
|
||||
* active interface.
|
||||
*
|
||||
* When close() is called OR when the interface is released, the underlying
|
||||
* resources must be freed.
|
||||
*/
|
||||
interface ICloseHandle {
|
||||
/**
|
||||
* Closes the handle.
|
||||
*
|
||||
* The call must not fail and must be issued by the client at most once.
|
||||
* Otherwise, the server must ignore subsequent calls.
|
||||
*/
|
||||
close();
|
||||
};
|
||||
56
automotive/can/1.0/default/Android.bp
Normal file
56
automotive/can/1.0/default/Android.bp
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
cc_defaults {
|
||||
name: "android.hardware.automotive.can@defaults",
|
||||
cpp_std: "experimental",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1",
|
||||
],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libutils",
|
||||
],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "android.hardware.automotive.can@1.0-service",
|
||||
init_rc: ["android.hardware.automotive.can@1.0-service.rc"],
|
||||
defaults: ["android.hardware.automotive.can@defaults"],
|
||||
vendor: true,
|
||||
relative_install_path: "hw",
|
||||
srcs: [
|
||||
"CanBus.cpp",
|
||||
"CanBusNative.cpp",
|
||||
"CanBusVirtual.cpp",
|
||||
"CanBusSlcan.cpp",
|
||||
"CanController.cpp",
|
||||
"CanSocket.cpp",
|
||||
"CloseHandle.cpp",
|
||||
"service.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"android.hardware.automotive.can@1.0",
|
||||
"libhidlbase",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.automotive.can@libnetdevice",
|
||||
"android.hardware.automotive@libc++fs",
|
||||
],
|
||||
}
|
||||
344
automotive/can/1.0/default/CanBus.cpp
Normal file
344
automotive/can/1.0/default/CanBus.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanBus.h"
|
||||
|
||||
#include "CloseHandle.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <libnetdevice/can.h>
|
||||
#include <libnetdevice/libnetdevice.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/error.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
/** Whether to log sent/received packets. */
|
||||
static constexpr bool kSuperVerbose = false;
|
||||
|
||||
Return<Result> CanBus::send(const CanMessage& message) {
|
||||
std::lock_guard<std::mutex> lck(mIsUpGuard);
|
||||
if (!mIsUp) return Result::INTERFACE_DOWN;
|
||||
|
||||
if (UNLIKELY(kSuperVerbose)) {
|
||||
LOG(VERBOSE) << "Sending " << toString(message);
|
||||
}
|
||||
|
||||
if (message.payload.size() > CAN_MAX_DLEN) return Result::PAYLOAD_TOO_LONG;
|
||||
|
||||
struct canfd_frame frame = {};
|
||||
frame.can_id = message.id;
|
||||
if (message.isExtendedId) frame.can_id |= CAN_EFF_FLAG;
|
||||
if (message.remoteTransmissionRequest) frame.can_id |= CAN_RTR_FLAG;
|
||||
frame.len = message.payload.size();
|
||||
memcpy(frame.data, message.payload.data(), message.payload.size());
|
||||
|
||||
if (!mSocket->send(frame)) return Result::TRANSMISSION_FAILURE;
|
||||
|
||||
return Result::OK;
|
||||
}
|
||||
|
||||
Return<void> CanBus::listen(const hidl_vec<CanMessageFilter>& filter,
|
||||
const sp<ICanMessageListener>& listenerCb, listen_cb _hidl_cb) {
|
||||
std::lock_guard<std::mutex> lck(mIsUpGuard);
|
||||
|
||||
if (listenerCb == nullptr) {
|
||||
_hidl_cb(Result::INVALID_ARGUMENTS, nullptr);
|
||||
return {};
|
||||
}
|
||||
if (!mIsUp) {
|
||||
_hidl_cb(Result::INTERFACE_DOWN, nullptr);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lckListeners(mMsgListenersGuard);
|
||||
|
||||
sp<CloseHandle> closeHandle = new CloseHandle([this, listenerCb]() {
|
||||
std::lock_guard<std::mutex> lck(mMsgListenersGuard);
|
||||
std::erase_if(mMsgListeners, [&](const auto& e) { return e.callback == listenerCb; });
|
||||
});
|
||||
mMsgListeners.emplace_back(CanMessageListener{listenerCb, filter, closeHandle});
|
||||
auto& listener = mMsgListeners.back();
|
||||
|
||||
// fix message IDs to have all zeros on bits not covered by mask
|
||||
std::for_each(listener.filter.begin(), listener.filter.end(),
|
||||
[](auto& rule) { rule.id &= rule.mask; });
|
||||
|
||||
_hidl_cb(Result::OK, closeHandle);
|
||||
return {};
|
||||
}
|
||||
|
||||
CanBus::CanBus() {}
|
||||
|
||||
CanBus::CanBus(const std::string& ifname) : mIfname(ifname) {}
|
||||
|
||||
CanBus::~CanBus() {
|
||||
std::lock_guard<std::mutex> lck(mIsUpGuard);
|
||||
CHECK(!mIsUp) << "Interface is still up while being destroyed";
|
||||
|
||||
std::lock_guard<std::mutex> lckListeners(mMsgListenersGuard);
|
||||
CHECK(mMsgListeners.empty()) << "Listener list is not empty while interface is being destroyed";
|
||||
}
|
||||
|
||||
void CanBus::setErrorCallback(ErrorCallback errcb) {
|
||||
CHECK(!mIsUp) << "Can't set error callback while interface is up";
|
||||
CHECK(mErrCb == nullptr) << "Error callback is already set";
|
||||
mErrCb = errcb;
|
||||
CHECK(!mIsUp) << "Can't set error callback while interface is up";
|
||||
}
|
||||
|
||||
ICanController::Result CanBus::preUp() {
|
||||
return ICanController::Result::OK;
|
||||
}
|
||||
|
||||
bool CanBus::postDown() {
|
||||
return true;
|
||||
}
|
||||
|
||||
ICanController::Result CanBus::up() {
|
||||
std::lock_guard<std::mutex> lck(mIsUpGuard);
|
||||
|
||||
if (mIsUp) {
|
||||
LOG(WARNING) << "Interface is already up";
|
||||
return ICanController::Result::INVALID_STATE;
|
||||
}
|
||||
|
||||
const auto preResult = preUp();
|
||||
if (preResult != ICanController::Result::OK) return preResult;
|
||||
|
||||
const auto isUp = netdevice::isUp(mIfname);
|
||||
if (!isUp.has_value()) {
|
||||
// preUp() should prepare the interface (either create or make sure it's there)
|
||||
LOG(ERROR) << "Interface " << mIfname << " didn't get prepared";
|
||||
return ICanController::Result::BAD_INTERFACE_ID;
|
||||
}
|
||||
|
||||
if (!*isUp && !netdevice::up(mIfname)) {
|
||||
LOG(ERROR) << "Can't bring " << mIfname << " up";
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
mDownAfterUse = !*isUp;
|
||||
|
||||
using namespace std::placeholders;
|
||||
CanSocket::ReadCallback rdcb = std::bind(&CanBus::onRead, this, _1, _2);
|
||||
CanSocket::ErrorCallback errcb = std::bind(&CanBus::onError, this, _1);
|
||||
mSocket = CanSocket::open(mIfname, rdcb, errcb);
|
||||
if (!mSocket) {
|
||||
if (mDownAfterUse) netdevice::down(mIfname);
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
mIsUp = true;
|
||||
return ICanController::Result::OK;
|
||||
}
|
||||
|
||||
void CanBus::clearMsgListeners() {
|
||||
std::vector<wp<ICloseHandle>> listenersToClose;
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(mMsgListenersGuard);
|
||||
std::transform(mMsgListeners.begin(), mMsgListeners.end(),
|
||||
std::back_inserter(listenersToClose),
|
||||
[](const auto& e) { return e.closeHandle; });
|
||||
}
|
||||
|
||||
for (auto& weakListener : listenersToClose) {
|
||||
/* Between populating listenersToClose and calling close method here, some listeners might
|
||||
* have been already removed from the original mMsgListeners list (resulting in a dangling
|
||||
* weak pointer here). It's fine - we just want to clean them up. */
|
||||
auto listener = weakListener.promote();
|
||||
if (listener != nullptr) listener->close();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lck(mMsgListenersGuard);
|
||||
CHECK(mMsgListeners.empty()) << "Listeners list wasn't emptied";
|
||||
}
|
||||
|
||||
void CanBus::clearErrListeners() {
|
||||
std::lock_guard<std::mutex> lck(mErrListenersGuard);
|
||||
mErrListeners.clear();
|
||||
}
|
||||
|
||||
Return<sp<ICloseHandle>> CanBus::listenForErrors(const sp<ICanErrorListener>& listener) {
|
||||
if (listener == nullptr) {
|
||||
return new CloseHandle();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> upLck(mIsUpGuard);
|
||||
if (!mIsUp) {
|
||||
listener->onError(ErrorEvent::INTERFACE_DOWN, true);
|
||||
return new CloseHandle();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> errLck(mErrListenersGuard);
|
||||
mErrListeners.emplace_back(listener);
|
||||
|
||||
return new CloseHandle([this, listener]() {
|
||||
std::lock_guard<std::mutex> lck(mErrListenersGuard);
|
||||
std::erase(mErrListeners, listener);
|
||||
});
|
||||
}
|
||||
|
||||
bool CanBus::down() {
|
||||
std::lock_guard<std::mutex> lck(mIsUpGuard);
|
||||
|
||||
if (!mIsUp) {
|
||||
LOG(WARNING) << "Interface is already down";
|
||||
return false;
|
||||
}
|
||||
mIsUp = false;
|
||||
|
||||
clearMsgListeners();
|
||||
clearErrListeners();
|
||||
mSocket.reset();
|
||||
|
||||
bool success = true;
|
||||
|
||||
if (mDownAfterUse && !netdevice::down(mIfname)) {
|
||||
LOG(ERROR) << "Can't bring " << mIfname << " down";
|
||||
// don't return yet, let's try to do best-effort cleanup
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!postDown()) success = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine if a flag meets the requirements of a
|
||||
* FilterFlag. See definition of FilterFlag in types.hal
|
||||
*
|
||||
* \param filterFlag FilterFlag object to match flag against
|
||||
* \param flag bool object from CanMessage object
|
||||
*/
|
||||
static bool satisfiesFilterFlag(FilterFlag filterFlag, bool flag) {
|
||||
if (filterFlag == FilterFlag::DONT_CARE) return true;
|
||||
if (filterFlag == FilterFlag::SET) return flag;
|
||||
if (filterFlag == FilterFlag::NOT_SET) return !flag;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the filter set against message id.
|
||||
*
|
||||
* For details on the filters syntax, please see CanMessageFilter at
|
||||
* the HAL definition (types.hal).
|
||||
*
|
||||
* \param filter Filter to match against
|
||||
* \param id Message id to filter
|
||||
* \return true if the message id matches the filter, false otherwise
|
||||
*/
|
||||
static bool match(const hidl_vec<CanMessageFilter>& filter, CanMessageId id, bool isRtr,
|
||||
bool isExtendedId) {
|
||||
if (filter.size() == 0) return true;
|
||||
|
||||
bool anyNonExcludeRulePresent = false;
|
||||
bool anyNonExcludeRuleSatisfied = false;
|
||||
for (auto& rule : filter) {
|
||||
const bool satisfied = ((id & rule.mask) == rule.id) &&
|
||||
satisfiesFilterFlag(rule.rtr, isRtr) &&
|
||||
satisfiesFilterFlag(rule.extendedFormat, isExtendedId);
|
||||
|
||||
if (rule.exclude) {
|
||||
// Any excluded (blacklist) rule not being satisfied invalidates the whole filter set.
|
||||
if (satisfied) return false;
|
||||
} else {
|
||||
anyNonExcludeRulePresent = true;
|
||||
if (satisfied) anyNonExcludeRuleSatisfied = true;
|
||||
}
|
||||
}
|
||||
return !anyNonExcludeRulePresent || anyNonExcludeRuleSatisfied;
|
||||
}
|
||||
|
||||
void CanBus::notifyErrorListeners(ErrorEvent err, bool isFatal) {
|
||||
std::lock_guard<std::mutex> lck(mErrListenersGuard);
|
||||
for (auto& listener : mErrListeners) {
|
||||
if (!listener->onError(err, isFatal).isOk()) {
|
||||
LOG(WARNING) << "Failed to notify listener about error";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ErrorEvent parseErrorFrame(const struct canfd_frame& frame) {
|
||||
// decode error frame (to a degree)
|
||||
if ((frame.can_id & (CAN_ERR_BUSERROR | CAN_ERR_BUSOFF)) != 0) {
|
||||
return ErrorEvent::BUS_ERROR;
|
||||
}
|
||||
if ((frame.data[1] & CAN_ERR_CRTL_TX_OVERFLOW) != 0) {
|
||||
return ErrorEvent::TX_OVERFLOW;
|
||||
}
|
||||
if ((frame.data[1] & CAN_ERR_CRTL_RX_OVERFLOW) != 0) {
|
||||
return ErrorEvent::RX_OVERFLOW;
|
||||
}
|
||||
if ((frame.data[2] & CAN_ERR_PROT_OVERLOAD) != 0) {
|
||||
return ErrorEvent::BUS_OVERLOAD;
|
||||
}
|
||||
if ((frame.can_id & CAN_ERR_PROT) != 0) {
|
||||
return ErrorEvent::MALFORMED_INPUT;
|
||||
}
|
||||
if ((frame.can_id & (CAN_ERR_CRTL | CAN_ERR_TRX | CAN_ERR_RESTARTED)) != 0) {
|
||||
// "controller restarted" constitutes a HARDWARE_ERROR imo
|
||||
return ErrorEvent::HARDWARE_ERROR;
|
||||
}
|
||||
return ErrorEvent::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
void CanBus::onRead(const struct canfd_frame& frame, std::chrono::nanoseconds timestamp) {
|
||||
if ((frame.can_id & CAN_ERR_FLAG) != 0) {
|
||||
// error bit is set
|
||||
LOG(WARNING) << "CAN Error frame received";
|
||||
notifyErrorListeners(parseErrorFrame(frame), false);
|
||||
return;
|
||||
}
|
||||
|
||||
CanMessage message = {};
|
||||
message.id = frame.can_id & CAN_EFF_MASK; // mask out eff/rtr/err flags
|
||||
message.payload = hidl_vec<uint8_t>(frame.data, frame.data + frame.len);
|
||||
message.timestamp = timestamp.count();
|
||||
message.isExtendedId = (frame.can_id & CAN_EFF_FLAG) != 0;
|
||||
message.remoteTransmissionRequest = (frame.can_id & CAN_RTR_FLAG) != 0;
|
||||
|
||||
if (UNLIKELY(kSuperVerbose)) {
|
||||
LOG(VERBOSE) << "Got message " << toString(message);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lck(mMsgListenersGuard);
|
||||
for (auto& listener : mMsgListeners) {
|
||||
if (!match(listener.filter, message.id, message.remoteTransmissionRequest,
|
||||
message.isExtendedId))
|
||||
continue;
|
||||
if (!listener.callback->onReceive(message).isOk() && !listener.failedOnce) {
|
||||
listener.failedOnce = true;
|
||||
LOG(WARNING) << "Failed to notify listener about message";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CanBus::onError(int errnoVal) {
|
||||
auto eventType = ErrorEvent::HARDWARE_ERROR;
|
||||
|
||||
if (errnoVal == ENODEV || errnoVal == ENETDOWN) {
|
||||
mDownAfterUse = false;
|
||||
eventType = ErrorEvent::INTERFACE_DOWN;
|
||||
}
|
||||
notifyErrorListeners(eventType, true);
|
||||
|
||||
const auto errcb = mErrCb;
|
||||
if (errcb != nullptr) errcb();
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
112
automotive/can/1.0/default/CanBus.h
Normal file
112
automotive/can/1.0/default/CanBus.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CanSocket.h"
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICanBus.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICanController.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
struct CanBus : public ICanBus {
|
||||
using ErrorCallback = std::function<void()>;
|
||||
|
||||
virtual ~CanBus();
|
||||
|
||||
Return<Result> send(const CanMessage& message) override;
|
||||
Return<void> listen(const hidl_vec<CanMessageFilter>& filter,
|
||||
const sp<ICanMessageListener>& listener, listen_cb _hidl_cb) override;
|
||||
Return<sp<ICloseHandle>> listenForErrors(const sp<ICanErrorListener>& listener) override;
|
||||
|
||||
void setErrorCallback(ErrorCallback errcb);
|
||||
ICanController::Result up();
|
||||
bool down();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Blank constructor, since some interface types (such as SLCAN) don't get a name until after
|
||||
* being initialized.
|
||||
*
|
||||
* If using this constructor, you MUST initialize mIfname prior to the completion of preUp().
|
||||
*/
|
||||
CanBus();
|
||||
|
||||
CanBus(const std::string& ifname);
|
||||
|
||||
/**
|
||||
* Prepare the SocketCAN interface.
|
||||
*
|
||||
* After calling this method, mIfname network interface is available and ready to be brought up.
|
||||
*
|
||||
* \return OK on success, or an error state on failure. See ICanController::Result
|
||||
*/
|
||||
virtual ICanController::Result preUp();
|
||||
|
||||
/**
|
||||
* Cleanup after bringing the interface down.
|
||||
*
|
||||
* This is a counterpart to preUp().
|
||||
*
|
||||
* \return true upon success and false upon failure
|
||||
*/
|
||||
virtual bool postDown();
|
||||
|
||||
/** Network interface name. */
|
||||
std::string mIfname;
|
||||
|
||||
private:
|
||||
struct CanMessageListener {
|
||||
sp<ICanMessageListener> callback;
|
||||
hidl_vec<CanMessageFilter> filter;
|
||||
wp<ICloseHandle> closeHandle;
|
||||
bool failedOnce = false;
|
||||
};
|
||||
void clearMsgListeners();
|
||||
void clearErrListeners();
|
||||
|
||||
void notifyErrorListeners(ErrorEvent err, bool isFatal);
|
||||
|
||||
void onRead(const struct canfd_frame& frame, std::chrono::nanoseconds timestamp);
|
||||
void onError(int errnoVal);
|
||||
|
||||
std::mutex mMsgListenersGuard;
|
||||
std::vector<CanMessageListener> mMsgListeners GUARDED_BY(mMsgListenersGuard);
|
||||
|
||||
std::mutex mErrListenersGuard;
|
||||
std::vector<sp<ICanErrorListener>> mErrListeners GUARDED_BY(mErrListenersGuard);
|
||||
|
||||
std::unique_ptr<CanSocket> mSocket;
|
||||
bool mDownAfterUse;
|
||||
|
||||
/**
|
||||
* Guard for up flag is required to be held for entire time when the interface is being used
|
||||
* (i.e. message being sent), because we don't want the interface to be torn down while
|
||||
* executing that operation.
|
||||
*/
|
||||
std::mutex mIsUpGuard;
|
||||
bool mIsUp GUARDED_BY(mIsUpGuard) = false;
|
||||
|
||||
ErrorCallback mErrCb;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
52
automotive/can/1.0/default/CanBusNative.cpp
Normal file
52
automotive/can/1.0/default/CanBusNative.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanBusNative.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <libnetdevice/can.h>
|
||||
#include <libnetdevice/libnetdevice.h>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
CanBusNative::CanBusNative(const std::string& ifname, uint32_t bitrate)
|
||||
: CanBus(ifname), mBitrate(bitrate) {}
|
||||
|
||||
ICanController::Result CanBusNative::preUp() {
|
||||
if (!netdevice::exists(mIfname)) {
|
||||
LOG(ERROR) << "Interface " << mIfname << " doesn't exist";
|
||||
return ICanController::Result::BAD_INTERFACE_ID;
|
||||
}
|
||||
|
||||
if (mBitrate == 0) {
|
||||
// interface is already up and we just want to register it
|
||||
return ICanController::Result::OK;
|
||||
}
|
||||
|
||||
if (!netdevice::down(mIfname)) {
|
||||
LOG(ERROR) << "Can't bring " << mIfname << " down (to configure it)";
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!netdevice::can::setBitrate(mIfname, mBitrate)) {
|
||||
LOG(ERROR) << "Can't set bitrate " << mBitrate << " for " << mIfname;
|
||||
return ICanController::Result::BAD_BITRATE;
|
||||
}
|
||||
|
||||
return ICanController::Result::OK;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
33
automotive/can/1.0/default/CanBusNative.h
Normal file
33
automotive/can/1.0/default/CanBusNative.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CanBus.h"
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
struct CanBusNative : public CanBus {
|
||||
CanBusNative(const std::string& ifname, uint32_t bitrate);
|
||||
|
||||
protected:
|
||||
virtual ICanController::Result preUp() override;
|
||||
|
||||
private:
|
||||
const uint32_t mBitrate;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
171
automotive/can/1.0/default/CanBusSlcan.cpp
Normal file
171
automotive/can/1.0/default/CanBusSlcan.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanBusSlcan.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <libnetdevice/can.h>
|
||||
#include <libnetdevice/libnetdevice.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <termios.h>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
namespace slcanprotocol {
|
||||
static const std::string kOpenCommand = "O\r";
|
||||
static const std::string kCloseCommand = "C\r";
|
||||
static constexpr int kSlcanDiscipline = N_SLCAN;
|
||||
static constexpr int kDefaultDiscipline = N_TTY;
|
||||
|
||||
static const std::map<uint32_t, std::string> kBitrateCommands = {
|
||||
{10000, "C\rS0\r"}, {20000, "C\rS1\r"}, {50000, "C\rS2\r"},
|
||||
{100000, "C\rS3\r"}, {125000, "C\rS4\r"}, {250000, "C\rS5\r"},
|
||||
{500000, "C\rS6\r"}, {800000, "C\rS7\r"}, {1000000, "C\rS8\r"}};
|
||||
} // namespace slcanprotocol
|
||||
|
||||
/**
|
||||
* Serial Line CAN constructor
|
||||
* \param string uartName - name of slcan device (e.x. /dev/ttyUSB0)
|
||||
* \param uint32_t bitrate - speed of the CAN bus (125k = MSCAN, 500k = HSCAN)
|
||||
*/
|
||||
CanBusSlcan::CanBusSlcan(const std::string& uartName, uint32_t bitrate)
|
||||
: CanBus(), mUartName(uartName), kBitrate(bitrate) {}
|
||||
|
||||
/** helper function to update CanBusSlcan object's iface name */
|
||||
ICanController::Result CanBusSlcan::updateIfaceName(base::unique_fd& uartFd) {
|
||||
struct ifreq ifrequest = {};
|
||||
/*
|
||||
* Fetching the iface name with an ioctl won't interfere with an open socketCAN iface attached
|
||||
* to this tty. This is important in the event we are trying to register a SLCAN based iface
|
||||
* that has already been configured and brought up.
|
||||
*/
|
||||
if (ioctl(uartFd.get(), SIOCGIFNAME, ifrequest.ifr_name) < 0) {
|
||||
PLOG(ERROR) << "Failed to get the name of the created device";
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// Update the CanBus object with name that was assigned to it
|
||||
mIfname = ifrequest.ifr_name;
|
||||
return ICanController::Result::OK;
|
||||
}
|
||||
|
||||
ICanController::Result CanBusSlcan::preUp() {
|
||||
// verify valid bitrate and translate to serial command format
|
||||
std::optional<std::string> canBitrateCommand = std::nullopt;
|
||||
if (kBitrate != 0) {
|
||||
const auto lookupIt = slcanprotocol::kBitrateCommands.find(kBitrate);
|
||||
if (lookupIt == slcanprotocol::kBitrateCommands.end()) {
|
||||
return ICanController::Result::BAD_BITRATE;
|
||||
}
|
||||
canBitrateCommand = lookupIt->second;
|
||||
}
|
||||
|
||||
/* Attempt to open the uart in r/w without blocking or becoming the
|
||||
* controlling terminal */
|
||||
mFd = base::unique_fd(open(mUartName.c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY));
|
||||
if (!mFd.ok()) {
|
||||
PLOG(ERROR) << "SLCAN Failed to open " << mUartName;
|
||||
return ICanController::Result::BAD_INTERFACE_ID;
|
||||
}
|
||||
|
||||
// If the device is already up, update the iface name in our CanBusSlcan object
|
||||
if (kBitrate == 0) {
|
||||
return updateIfaceName(mFd);
|
||||
}
|
||||
|
||||
// blank terminal settings and pull them from the device
|
||||
struct termios terminalSettings = {};
|
||||
if (tcgetattr(mFd.get(), &terminalSettings) < 0) {
|
||||
PLOG(ERROR) << "Failed to read attrs of" << mUartName;
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// change settings to raw mode
|
||||
cfmakeraw(&terminalSettings);
|
||||
|
||||
// disable software flow control
|
||||
terminalSettings.c_iflag &= ~IXOFF;
|
||||
// enable hardware flow control
|
||||
terminalSettings.c_cflag |= CRTSCTS;
|
||||
|
||||
struct serial_struct serialSettings;
|
||||
// get serial settings
|
||||
if (ioctl(mFd.get(), TIOCGSERIAL, &serialSettings) < 0) {
|
||||
PLOG(ERROR) << "Failed to read serial settings from " << mUartName;
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
// set low latency mode
|
||||
serialSettings.flags |= ASYNC_LOW_LATENCY;
|
||||
// apply serial settings
|
||||
if (ioctl(mFd.get(), TIOCSSERIAL, &serialSettings) < 0) {
|
||||
PLOG(ERROR) << "Failed to set low latency mode on " << mUartName;
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
/* TCSADRAIN applies settings after we finish writing the rest of our
|
||||
* changes (as opposed to TCSANOW, which changes immediately) */
|
||||
if (tcsetattr(mFd.get(), TCSADRAIN, &terminalSettings) < 0) {
|
||||
PLOG(ERROR) << "Failed to apply terminal settings to " << mUartName;
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// apply speed setting for CAN
|
||||
if (write(mFd.get(), canBitrateCommand->c_str(), canBitrateCommand->length()) <= 0) {
|
||||
PLOG(ERROR) << "Failed to apply CAN bitrate";
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// TODO(b/144775286): set open flag & support listen only
|
||||
if (write(mFd.get(), slcanprotocol::kOpenCommand.c_str(),
|
||||
slcanprotocol::kOpenCommand.length()) <= 0) {
|
||||
PLOG(ERROR) << "Failed to set open flag";
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// set line discipline to slcan
|
||||
if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kSlcanDiscipline) < 0) {
|
||||
PLOG(ERROR) << "Failed to set line discipline to slcan";
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// Update the CanBus object with name that was assigned to it
|
||||
return updateIfaceName(mFd);
|
||||
}
|
||||
|
||||
bool CanBusSlcan::postDown() {
|
||||
// reset the line discipline to TTY mode
|
||||
if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kDefaultDiscipline) < 0) {
|
||||
LOG(ERROR) << "Failed to reset line discipline!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// issue the close command
|
||||
if (write(mFd.get(), slcanprotocol::kCloseCommand.c_str(),
|
||||
slcanprotocol::kCloseCommand.length()) <= 0) {
|
||||
LOG(ERROR) << "Failed to close tty!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// close our unique_fd
|
||||
mFd.reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
42
automotive/can/1.0/default/CanBusSlcan.h
Normal file
42
automotive/can/1.0/default/CanBusSlcan.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <linux/serial.h>
|
||||
#include <linux/tty.h>
|
||||
#include <net/if.h>
|
||||
#include <termios.h>
|
||||
#include "CanBus.h"
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
struct CanBusSlcan : public CanBus {
|
||||
CanBusSlcan(const std::string& uartName, uint32_t bitrate);
|
||||
|
||||
protected:
|
||||
virtual ICanController::Result preUp() override;
|
||||
virtual bool postDown() override;
|
||||
|
||||
private:
|
||||
ICanController::Result updateIfaceName(base::unique_fd& uartFd);
|
||||
|
||||
const std::string mUartName;
|
||||
const uint32_t kBitrate;
|
||||
base::unique_fd mFd;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
50
automotive/can/1.0/default/CanBusVirtual.cpp
Normal file
50
automotive/can/1.0/default/CanBusVirtual.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanBusVirtual.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <libnetdevice/libnetdevice.h>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
CanBusVirtual::CanBusVirtual(const std::string& ifname) : CanBus(ifname) {}
|
||||
|
||||
ICanController::Result CanBusVirtual::preUp() {
|
||||
if (netdevice::exists(mIfname)) return ICanController::Result::OK;
|
||||
|
||||
LOG(DEBUG) << "Virtual interface " << mIfname << " doesn't exist, creating...";
|
||||
mWasCreated = true;
|
||||
if (!netdevice::add(mIfname, "vcan")) {
|
||||
LOG(ERROR) << "Can't create vcan interface " << mIfname;
|
||||
return ICanController::Result::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
return ICanController::Result::OK;
|
||||
}
|
||||
|
||||
bool CanBusVirtual::postDown() {
|
||||
if (mWasCreated) {
|
||||
mWasCreated = false;
|
||||
if (!netdevice::del(mIfname)) {
|
||||
LOG(ERROR) << "Couldn't remove vcan interface " << mIfname;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
34
automotive/can/1.0/default/CanBusVirtual.h
Normal file
34
automotive/can/1.0/default/CanBusVirtual.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CanBus.h"
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
struct CanBusVirtual : public CanBus {
|
||||
CanBusVirtual(const std::string& ifname);
|
||||
|
||||
protected:
|
||||
virtual ICanController::Result preUp() override;
|
||||
virtual bool postDown() override;
|
||||
|
||||
private:
|
||||
bool mWasCreated = false;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
330
automotive/can/1.0/default/CanController.cpp
Normal file
330
automotive/can/1.0/default/CanController.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanController.h"
|
||||
|
||||
#include "CanBusNative.h"
|
||||
#include "CanBusSlcan.h"
|
||||
#include "CanBusVirtual.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android/hidl/manager/1.2/IServiceManager.h>
|
||||
|
||||
#include <automotive/filesystem>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
using IfId = ICanController::BusConfig::InterfaceId;
|
||||
using IfIdDisc = ICanController::BusConfig::InterfaceId::hidl_discriminator;
|
||||
namespace fs = android::hardware::automotive::filesystem;
|
||||
|
||||
namespace fsErrors {
|
||||
static const std::error_code ok;
|
||||
static const std::error_code eperm(EPERM, std::generic_category());
|
||||
static const std::error_code enoent(ENOENT, std::generic_category());
|
||||
static const std::error_code eacces(EACCES, std::generic_category());
|
||||
} // namespace fsErrors
|
||||
|
||||
/* In the /sys/devices tree, there are files called "serial", which contain the serial numbers
|
||||
* for various devices. The exact location inside of this directory is dependent upon the
|
||||
* hardware we are running on, so we have to start from /sys/devices and work our way down. */
|
||||
static const fs::path kDevPath("/sys/devices/");
|
||||
static const std::regex kTtyRe("^tty[A-Z]+[0-9]+$");
|
||||
static constexpr auto kOpts = ~(fs::directory_options::follow_directory_symlink |
|
||||
fs::directory_options::skip_permission_denied);
|
||||
|
||||
/**
|
||||
* A helper object to associate the interface name and type of a USB to CAN adapter.
|
||||
*/
|
||||
struct UsbCanIface {
|
||||
ICanController::InterfaceType iftype;
|
||||
std::string ifaceName;
|
||||
};
|
||||
|
||||
Return<void> CanController::getSupportedInterfaceTypes(getSupportedInterfaceTypes_cb _hidl_cb) {
|
||||
_hidl_cb({ICanController::InterfaceType::VIRTUAL, ICanController::InterfaceType::SOCKETCAN,
|
||||
ICanController::InterfaceType::SLCAN});
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool isValidName(const std::string& name) {
|
||||
static const std::regex nameRE("^[a-zA-Z0-9_]{1,32}$");
|
||||
return std::regex_match(name, nameRE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a UsbCanIface object, get the ifaceName given the serialPath.
|
||||
*
|
||||
* \param serialPath - Absolute path to a "serial" file for a given device in /sys.
|
||||
* \return A populated UsbCanIface. On failure, nullopt is returned.
|
||||
*/
|
||||
static std::optional<UsbCanIface> getIfaceName(fs::path serialPath) {
|
||||
std::error_code fsStatus;
|
||||
// Since the path is to a file called "serial", we need to search its parent directory.
|
||||
fs::recursive_directory_iterator fsItr(serialPath.parent_path(), kOpts, fsStatus);
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
LOG(ERROR) << "Failed to open " << serialPath.parent_path();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
|
||||
fsItr.increment(fsStatus)) {
|
||||
/* We want either a directory called "net" or a directory that looks like tty<something>, so
|
||||
* skip files. */
|
||||
bool isDir = fsItr->is_directory(fsStatus);
|
||||
if (fsStatus != fsErrors::ok || !isDir) continue;
|
||||
|
||||
/* path() returns an iterator that steps through directories from / to the leaf.
|
||||
* end() returns one past the leaf of the path, but we want the leaf. Decrementing the
|
||||
* path gives us a pointer to the leaf, which we then dereference.*/
|
||||
std::string currentDir = *(--(fsItr->path().end()));
|
||||
if (currentDir == "net") {
|
||||
/* This device is a SocketCAN device. The iface name is the only directory under
|
||||
* net/. Multiple directories under net/ is an error.*/
|
||||
fs::directory_iterator netItr(fsItr->path(), kOpts, fsStatus);
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
LOG(ERROR) << "Failed to open " << fsItr->path() << " to get net name!";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Get the leaf of the path. This is the interface name, assuming it's the only leaf.
|
||||
std::string netName = *(--(netItr->path().end()));
|
||||
|
||||
// Check if there is more than one item in net/
|
||||
netItr.increment(fsStatus);
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
// It's possible we have a valid net name, but this is most likely an error.
|
||||
LOG(ERROR) << "Failed to verify " << fsItr->path() << " has valid net name!";
|
||||
return std::nullopt;
|
||||
}
|
||||
if (netItr != fs::directory_iterator()) {
|
||||
// There should never be more than one name under net/
|
||||
LOG(ERROR) << "Found more than one net name in " << fsItr->path() << "!";
|
||||
return std::nullopt;
|
||||
}
|
||||
return {{ICanController::InterfaceType::SOCKETCAN, netName}};
|
||||
} else if (std::regex_match(currentDir, kTtyRe)) {
|
||||
// This device is a USB serial device, and currentDir is the tty name.
|
||||
return {{ICanController::InterfaceType::SLCAN, "/dev/" + currentDir}};
|
||||
}
|
||||
}
|
||||
|
||||
// check if the loop above exited due to a c++fs error.
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
LOG(ERROR) << "Failed search filesystem: " << fsStatus;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to read the serial number from a "serial" file in /sys/devices/
|
||||
*
|
||||
* \param serialnoPath - path to the file to read.
|
||||
* \return the serial number, or nullopt on failure.
|
||||
*/
|
||||
static std::optional<std::string> readSerialNo(const std::string& serialnoPath) {
|
||||
std::ifstream serialnoStream(serialnoPath);
|
||||
std::string serialno;
|
||||
if (!serialnoStream.good()) {
|
||||
LOG(ERROR) << "Failed to read serial number from " << serialnoPath;
|
||||
return std::nullopt;
|
||||
}
|
||||
std::getline(serialnoStream, serialno);
|
||||
return serialno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for USB devices found in /sys/devices/, and attempts to find a device matching the
|
||||
* provided list of serial numbers.
|
||||
*
|
||||
* \param configSerialnos - a list of serial number (suffixes) from the HAL config.
|
||||
* \param iftype - the type of the interface to be located.
|
||||
* \return a matching USB device. On failure, std::nullopt is returned.
|
||||
*/
|
||||
static std::optional<UsbCanIface> findUsbDevice(const hidl_vec<hidl_string>& configSerialnos) {
|
||||
std::error_code fsStatus;
|
||||
fs::recursive_directory_iterator fsItr(kDevPath, kOpts, fsStatus);
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
LOG(ERROR) << "Failed to open " << kDevPath;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
|
||||
fsItr.increment(fsStatus)) {
|
||||
// We want to find a file called "serial", which is in a directory somewhere. Skip files.
|
||||
bool isDir = fsItr->is_directory(fsStatus);
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
LOG(ERROR) << "Failed check if " << fsStatus;
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!isDir) continue;
|
||||
|
||||
auto serialnoPath = fsItr->path() / "serial";
|
||||
bool isReg = fs::is_regular_file(serialnoPath, fsStatus);
|
||||
|
||||
/* Make sure we have permissions to this directory, ignore enoent, since the file
|
||||
* "serial" may not exist, which is ok. */
|
||||
if (fsStatus == fsErrors::eperm || fsStatus == fsErrors::eacces) {
|
||||
/* This means we don't have access to this directory. If we recurse into it, this
|
||||
* will cause the iterator to loose its state and we'll crash. */
|
||||
fsItr.disable_recursion_pending();
|
||||
continue;
|
||||
}
|
||||
if (fsStatus == fsErrors::enoent) continue;
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
LOG(WARNING) << "An unexpected error occurred while checking for serialno: "
|
||||
<< fsStatus;
|
||||
continue;
|
||||
}
|
||||
if (!isReg) continue;
|
||||
|
||||
// we found a serial number
|
||||
auto serialno = readSerialNo(serialnoPath);
|
||||
if (!serialno.has_value()) continue;
|
||||
|
||||
// see if the serial number exists in the config
|
||||
for (auto&& cfgSn : configSerialnos) {
|
||||
if (serialno->ends_with(std::string(cfgSn))) {
|
||||
auto ifaceInfo = getIfaceName(serialnoPath);
|
||||
if (!ifaceInfo.has_value()) break;
|
||||
return ifaceInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fsStatus != fsErrors::ok) {
|
||||
LOG(ERROR) << "Error searching filesystem: " << fsStatus;
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Return<ICanController::Result> CanController::upInterface(const ICanController::BusConfig& config) {
|
||||
LOG(VERBOSE) << "Attempting to bring interface up: " << toString(config);
|
||||
|
||||
std::lock_guard<std::mutex> lck(mCanBusesGuard);
|
||||
|
||||
if (!isValidName(config.name)) {
|
||||
LOG(ERROR) << "Bus name " << config.name << " is invalid";
|
||||
return ICanController::Result::BAD_SERVICE_NAME;
|
||||
}
|
||||
|
||||
if (mCanBuses.find(config.name) != mCanBuses.end()) {
|
||||
LOG(ERROR) << "Bus " << config.name << " is already up";
|
||||
return ICanController::Result::INVALID_STATE;
|
||||
}
|
||||
|
||||
sp<CanBus> busService;
|
||||
|
||||
// SocketCAN native type interface.
|
||||
if (config.interfaceId.getDiscriminator() == IfIdDisc::socketcan) {
|
||||
auto& socketcan = config.interfaceId.socketcan();
|
||||
std::string ifaceName;
|
||||
if (socketcan.getDiscriminator() == IfId::Socketcan::hidl_discriminator::serialno) {
|
||||
// Configure by serial number.
|
||||
auto selectedDevice = findUsbDevice(socketcan.serialno());
|
||||
// verify the returned device is the correct one
|
||||
if (!selectedDevice.has_value() ||
|
||||
selectedDevice->iftype != ICanController::InterfaceType::SOCKETCAN) {
|
||||
return ICanController::Result::BAD_INTERFACE_ID;
|
||||
}
|
||||
ifaceName = selectedDevice->ifaceName;
|
||||
} else {
|
||||
// configure by iface name.
|
||||
ifaceName = socketcan.ifname();
|
||||
}
|
||||
busService = new CanBusNative(ifaceName, config.bitrate);
|
||||
}
|
||||
// Virtual interface.
|
||||
else if (config.interfaceId.getDiscriminator() == IfIdDisc::virtualif) {
|
||||
busService = new CanBusVirtual(config.interfaceId.virtualif().ifname);
|
||||
}
|
||||
// SLCAN interface.
|
||||
else if (config.interfaceId.getDiscriminator() == IfIdDisc::slcan) {
|
||||
auto& slcan = config.interfaceId.slcan();
|
||||
std::string ttyName;
|
||||
if (slcan.getDiscriminator() == IfId::Slcan::hidl_discriminator::serialno) {
|
||||
// Configure by serial number.
|
||||
auto selectedDevice = findUsbDevice(slcan.serialno());
|
||||
if (!selectedDevice.has_value() ||
|
||||
selectedDevice->iftype != ICanController::InterfaceType::SLCAN) {
|
||||
return ICanController::Result::BAD_INTERFACE_ID;
|
||||
}
|
||||
ttyName = selectedDevice->ifaceName;
|
||||
} else {
|
||||
// Configure by tty name.
|
||||
ttyName = slcan.ttyname();
|
||||
}
|
||||
busService = new CanBusSlcan(ttyName, config.bitrate);
|
||||
} else {
|
||||
return ICanController::Result::NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
busService->setErrorCallback([this, name = config.name]() { downInterface(name); });
|
||||
|
||||
const auto result = busService->up();
|
||||
if (result != ICanController::Result::OK) return result;
|
||||
|
||||
if (busService->registerAsService(config.name) != OK) {
|
||||
LOG(ERROR) << "Failed to register ICanBus/" << config.name;
|
||||
if (!busService->down()) {
|
||||
LOG(WARNING) << "Failed to bring down CAN bus that failed to register";
|
||||
}
|
||||
return ICanController::Result::BAD_SERVICE_NAME;
|
||||
}
|
||||
|
||||
mCanBuses[config.name] = busService;
|
||||
|
||||
return ICanController::Result::OK;
|
||||
}
|
||||
|
||||
static bool unregisterCanBusService(const hidl_string& name, sp<CanBus> busService) {
|
||||
auto manager = hidl::manager::V1_2::IServiceManager::getService();
|
||||
if (!manager) return false;
|
||||
const auto res = manager->tryUnregister(ICanBus::descriptor, name, busService);
|
||||
if (!res.isOk()) return false;
|
||||
return res;
|
||||
}
|
||||
|
||||
Return<bool> CanController::downInterface(const hidl_string& name) {
|
||||
LOG(VERBOSE) << "Attempting to bring interface down: " << name;
|
||||
|
||||
std::lock_guard<std::mutex> lck(mCanBusesGuard);
|
||||
|
||||
auto busEntry = mCanBuses.extract(name);
|
||||
if (!busEntry) {
|
||||
LOG(WARNING) << "Interface " << name << " is not up";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto success = true;
|
||||
|
||||
if (!unregisterCanBusService(name, busEntry.mapped())) {
|
||||
LOG(ERROR) << "Couldn't unregister " << name;
|
||||
// don't return yet, let's try to do best-effort cleanup
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!busEntry.mapped()->down()) {
|
||||
LOG(ERROR) << "Couldn't bring " << name << " down";
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
36
automotive/can/1.0/default/CanController.h
Normal file
36
automotive/can/1.0/default/CanController.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CanBus.h"
|
||||
|
||||
#include <android/hardware/automotive/can/1.0/ICanController.h>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
struct CanController : public ICanController {
|
||||
Return<void> getSupportedInterfaceTypes(getSupportedInterfaceTypes_cb _hidl_cb) override;
|
||||
|
||||
Return<ICanController::Result> upInterface(const ICanController::BusConfig& config) override;
|
||||
Return<bool> downInterface(const hidl_string& name) override;
|
||||
|
||||
private:
|
||||
std::mutex mCanBusesGuard;
|
||||
std::map<std::string, sp<CanBus>> mCanBuses GUARDED_BY(mCanBusesGuard);
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
150
automotive/can/1.0/default/CanSocket.cpp
Normal file
150
automotive/can/1.0/default/CanSocket.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanSocket.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <libnetdevice/can.h>
|
||||
#include <libnetdevice/libnetdevice.h>
|
||||
#include <linux/can.h>
|
||||
#include <utils/SystemClock.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
/* How frequently the read thread checks whether the interface was asked to be down.
|
||||
*
|
||||
* Note: This does *not* affect read timing or bandwidth, just CPU load vs time to
|
||||
* down the interface. */
|
||||
static constexpr auto kReadPooling = 100ms;
|
||||
|
||||
std::unique_ptr<CanSocket> CanSocket::open(const std::string& ifname, ReadCallback rdcb,
|
||||
ErrorCallback errcb) {
|
||||
auto sock = netdevice::can::socket(ifname);
|
||||
if (!sock.ok()) {
|
||||
LOG(ERROR) << "Can't open CAN socket on " << ifname;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Can't use std::make_unique due to private CanSocket constructor.
|
||||
return std::unique_ptr<CanSocket>(new CanSocket(std::move(sock), rdcb, errcb));
|
||||
}
|
||||
|
||||
CanSocket::CanSocket(base::unique_fd socket, ReadCallback rdcb, ErrorCallback errcb)
|
||||
: mReadCallback(rdcb),
|
||||
mErrorCallback(errcb),
|
||||
mSocket(std::move(socket)),
|
||||
mReaderThread(&CanSocket::readerThread, this) {}
|
||||
|
||||
CanSocket::~CanSocket() {
|
||||
mStopReaderThread = true;
|
||||
|
||||
/* CanSocket can be brought down as a result of read failure, from the same thread,
|
||||
* so let's just detach and let it finish on its own. */
|
||||
if (mReaderThreadFinished) {
|
||||
mReaderThread.detach();
|
||||
} else {
|
||||
mReaderThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool CanSocket::send(const struct canfd_frame& frame) {
|
||||
const auto res = write(mSocket.get(), &frame, CAN_MTU);
|
||||
if (res < 0) {
|
||||
PLOG(DEBUG) << "CanSocket send failed";
|
||||
return false;
|
||||
}
|
||||
if (res != CAN_MTU) {
|
||||
LOG(DEBUG) << "CanSocket sent wrong number of bytes: " << res;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct timeval toTimeval(std::chrono::microseconds t) {
|
||||
struct timeval tv;
|
||||
tv.tv_sec = t / 1s;
|
||||
tv.tv_usec = (t % 1s) / 1us;
|
||||
return tv;
|
||||
}
|
||||
|
||||
static int selectRead(const base::unique_fd& fd, std::chrono::microseconds timeout) {
|
||||
auto timeouttv = toTimeval(timeout);
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(fd.get(), &readfds);
|
||||
return select(fd.get() + 1, &readfds, nullptr, nullptr, &timeouttv);
|
||||
}
|
||||
|
||||
void CanSocket::readerThread() {
|
||||
LOG(VERBOSE) << "Reader thread started";
|
||||
int errnoCopy = 0;
|
||||
|
||||
while (!mStopReaderThread) {
|
||||
/* The ideal would be to have a blocking read(3) call and interrupt it with shutdown(3).
|
||||
* This is unfortunately not supported for SocketCAN, so we need to rely on select(3). */
|
||||
const auto sel = selectRead(mSocket, kReadPooling);
|
||||
if (sel == 0) continue; // timeout
|
||||
if (sel == -1) {
|
||||
PLOG(ERROR) << "Select failed";
|
||||
break;
|
||||
}
|
||||
|
||||
struct canfd_frame frame;
|
||||
const auto nbytes = read(mSocket.get(), &frame, CAN_MTU);
|
||||
|
||||
/* We could use SIOCGSTAMP to get a precise UNIX timestamp for a given packet, but what
|
||||
* we really need is a time since boot. There is no direct way to convert between these
|
||||
* clocks. We could implement a class to calculate the difference between the clocks
|
||||
* (querying both several times and picking the smallest difference); apply the difference
|
||||
* to a SIOCGSTAMP returned value; re-synchronize if the elapsed time is too much in the
|
||||
* past (indicating the UNIX timestamp might have been adjusted).
|
||||
*
|
||||
* Apart from the added complexity, it's possible the added calculations and system calls
|
||||
* would add so much time to the processing pipeline so the precision of the reported time
|
||||
* was buried under the subsystem latency. Let's just use a local time since boot here and
|
||||
* leave precise hardware timestamps for custom proprietary implementations (if needed). */
|
||||
const std::chrono::nanoseconds ts(elapsedRealtimeNano());
|
||||
|
||||
if (nbytes != CAN_MTU) {
|
||||
if (nbytes >= 0) {
|
||||
LOG(ERROR) << "Failed to read CAN packet, got " << nbytes << " bytes";
|
||||
break;
|
||||
}
|
||||
if (errno == EAGAIN) continue;
|
||||
|
||||
errnoCopy = errno;
|
||||
PLOG(ERROR) << "Failed to read CAN packet";
|
||||
break;
|
||||
}
|
||||
|
||||
mReadCallback(frame, ts);
|
||||
}
|
||||
|
||||
bool failed = !mStopReaderThread;
|
||||
auto errCb = mErrorCallback;
|
||||
mReaderThreadFinished = true;
|
||||
|
||||
// Don't access any fields from here, see CanSocket::~CanSocket comment about detached thread
|
||||
if (failed) errCb(errnoCopy);
|
||||
|
||||
LOG(VERBOSE) << "Reader thread stopped";
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
69
automotive/can/1.0/default/CanSocket.h
Normal file
69
automotive/can/1.0/default/CanSocket.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <linux/can.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
/** Wrapper around SocketCAN socket. */
|
||||
struct CanSocket {
|
||||
using ReadCallback = std::function<void(const struct canfd_frame&, std::chrono::nanoseconds)>;
|
||||
using ErrorCallback = std::function<void(int errnoVal)>;
|
||||
|
||||
/**
|
||||
* Open and bind SocketCAN socket.
|
||||
*
|
||||
* \param ifname SocketCAN network interface name (such as can0)
|
||||
* \param rdcb Callback on received messages
|
||||
* \param errcb Callback on socket failure
|
||||
* \return Socket instance, or nullptr if it wasn't possible to open one
|
||||
*/
|
||||
static std::unique_ptr<CanSocket> open(const std::string& ifname, ReadCallback rdcb,
|
||||
ErrorCallback errcb);
|
||||
virtual ~CanSocket();
|
||||
|
||||
/**
|
||||
* Send CAN frame.
|
||||
*
|
||||
* \param frame Frame to send
|
||||
* \return true in case of success, false otherwise
|
||||
*/
|
||||
bool send(const struct canfd_frame& frame);
|
||||
|
||||
private:
|
||||
CanSocket(base::unique_fd socket, ReadCallback rdcb, ErrorCallback errcb);
|
||||
void readerThread();
|
||||
|
||||
ReadCallback mReadCallback;
|
||||
ErrorCallback mErrorCallback;
|
||||
|
||||
const base::unique_fd mSocket;
|
||||
std::thread mReaderThread;
|
||||
std::atomic<bool> mStopReaderThread = false;
|
||||
std::atomic<bool> mReaderThreadFinished = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CanSocket);
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
35
automotive/can/1.0/default/CloseHandle.cpp
Normal file
35
automotive/can/1.0/default/CloseHandle.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CloseHandle.h"
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
CloseHandle::CloseHandle(Callback callback) : mCallback(callback) {}
|
||||
|
||||
CloseHandle::~CloseHandle() {
|
||||
close();
|
||||
}
|
||||
|
||||
Return<void> CloseHandle::close() {
|
||||
const auto wasClosed = mIsClosed.exchange(true);
|
||||
if (wasClosed) return {};
|
||||
|
||||
if (mCallback != nullptr) mCallback();
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
47
automotive/can/1.0/default/CloseHandle.h
Normal file
47
automotive/can/1.0/default/CloseHandle.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICloseHandle.h>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
/** Generic ICloseHandle implementation ignoring double-close events. */
|
||||
struct CloseHandle : public ICloseHandle {
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
/**
|
||||
* Create a handle with a callback.
|
||||
*
|
||||
* The callback is guaranteed to be called exactly once.
|
||||
*
|
||||
* \param callback Called on the first close() call, or on destruction of the handle
|
||||
*/
|
||||
CloseHandle(Callback callback = nullptr);
|
||||
virtual ~CloseHandle();
|
||||
|
||||
Return<void> close() override;
|
||||
|
||||
private:
|
||||
const Callback mCallback;
|
||||
std::atomic<bool> mIsClosed = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CloseHandle);
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
@@ -0,0 +1,5 @@
|
||||
service can-hal-1.0-service /vendor/bin/hw/android.hardware.automotive.can@1.0-service
|
||||
class hal
|
||||
capabilities NET_ADMIN
|
||||
user vehicle_network
|
||||
group system inet
|
||||
13
automotive/can/1.0/default/libc++fs/.clang-format
Normal file
13
automotive/can/1.0/default/libc++fs/.clang-format
Normal file
@@ -0,0 +1,13 @@
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: Cpp03
|
||||
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
PointerAlignment: Left
|
||||
|
||||
# Disable formatting options which may break tests.
|
||||
SortIncludes: false
|
||||
ReflowComments: false
|
||||
---
|
||||
61
automotive/can/1.0/default/libc++fs/Android.bp
Normal file
61
automotive/can/1.0/default/libc++fs/Android.bp
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright (C) 2020 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
// TODO(152067309): Stop building this yourself once it's ABI stable and has
|
||||
// been made vendor available. Just use libc++fs instead of this.
|
||||
|
||||
cc_defaults {
|
||||
name: "android.hardware.automotive@libc++fsdefaults",
|
||||
local_include_dirs: ["include"],
|
||||
export_include_dirs: ["include"],
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
],
|
||||
cppflags: [
|
||||
"-std=c++17",
|
||||
"-fexceptions",
|
||||
"-DLIBCXX_BUILDING_LIBCXXABI",
|
||||
"-D_LIBCPP_BUILDING_LIBRARY",
|
||||
],
|
||||
rtti: true,
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "android.hardware.automotive@libc++fs",
|
||||
recovery_available: true,
|
||||
vendor: true,
|
||||
defaults: ["android.hardware.automotive@libc++fsdefaults"],
|
||||
srcs: [
|
||||
"src/filesystem/directory_iterator.cpp",
|
||||
"src/filesystem/operations.cpp",
|
||||
],
|
||||
multilib: {
|
||||
lib32: {
|
||||
// off_t usage is constrained to within the libc++ source (not the
|
||||
// headers), so we can build the filesystem library with a 64-bit
|
||||
// off_t on LP32 to get large file support without needing all users
|
||||
// of the library to match.
|
||||
cflags: ["-D_FILE_OFFSET_BITS=64"],
|
||||
},
|
||||
},
|
||||
target: {
|
||||
windows: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
2699
automotive/can/1.0/default/libc++fs/include/automotive/filesystem
Normal file
2699
automotive/can/1.0/default/libc++fs/include/automotive/filesystem
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,397 @@
|
||||
//===------------------ directory_iterator.cpp ----------------------------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is dual licensed under the MIT and the University of Illinois Open
|
||||
// Source Licenses. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
/* clang-format off */
|
||||
#include "automotive/filesystem"
|
||||
#include <__config>
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
#include <errno.h>
|
||||
|
||||
#include "filesystem_common.h"
|
||||
|
||||
namespace android::hardware::automotive::filesystem {
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
#if !defined(_LIBCPP_WIN32API)
|
||||
template <class DirEntT, class = decltype(DirEntT::d_type)>
|
||||
static file_type get_file_type(DirEntT* ent, int) {
|
||||
switch (ent->d_type) {
|
||||
case DT_BLK:
|
||||
return file_type::block;
|
||||
case DT_CHR:
|
||||
return file_type::character;
|
||||
case DT_DIR:
|
||||
return file_type::directory;
|
||||
case DT_FIFO:
|
||||
return file_type::fifo;
|
||||
case DT_LNK:
|
||||
return file_type::symlink;
|
||||
case DT_REG:
|
||||
return file_type::regular;
|
||||
case DT_SOCK:
|
||||
return file_type::socket;
|
||||
// Unlike in lstat, hitting "unknown" here simply means that the underlying
|
||||
// filesystem doesn't support d_type. Report is as 'none' so we correctly
|
||||
// set the cache to empty.
|
||||
case DT_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
return file_type::none;
|
||||
}
|
||||
|
||||
template <class DirEntT>
|
||||
static file_type get_file_type(DirEntT* ent, long) {
|
||||
return file_type::none;
|
||||
}
|
||||
|
||||
static pair<string_view, file_type> posix_readdir(DIR* dir_stream,
|
||||
error_code& ec) {
|
||||
struct dirent* dir_entry_ptr = nullptr;
|
||||
errno = 0; // zero errno in order to detect errors
|
||||
ec.clear();
|
||||
if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
|
||||
if (errno)
|
||||
ec = capture_errno();
|
||||
return {};
|
||||
} else {
|
||||
return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)};
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
static file_type get_file_type(const WIN32_FIND_DATA& data) {
|
||||
//auto attrs = data.dwFileAttributes;
|
||||
// FIXME(EricWF)
|
||||
return file_type::unknown;
|
||||
}
|
||||
static uintmax_t get_file_size(const WIN32_FIND_DATA& data) {
|
||||
return (data.nFileSizeHight * (MAXDWORD + 1)) + data.nFileSizeLow;
|
||||
}
|
||||
static file_time_type get_write_time(const WIN32_FIND_DATA& data) {
|
||||
ULARGE_INTEGER tmp;
|
||||
FILETIME& time = data.ftLastWriteTime;
|
||||
tmp.u.LowPart = time.dwLowDateTime;
|
||||
tmp.u.HighPart = time.dwHighDateTime;
|
||||
return file_time_type(file_time_type::duration(time.QuadPart));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
} // namespace detail
|
||||
|
||||
using detail::ErrorHandler;
|
||||
|
||||
#if defined(_LIBCPP_WIN32API)
|
||||
class __dir_stream {
|
||||
public:
|
||||
__dir_stream() = delete;
|
||||
__dir_stream& operator=(const __dir_stream&) = delete;
|
||||
|
||||
__dir_stream(__dir_stream&& __ds) noexcept : __stream_(__ds.__stream_),
|
||||
__root_(move(__ds.__root_)),
|
||||
__entry_(move(__ds.__entry_)) {
|
||||
__ds.__stream_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
__dir_stream(const path& root, directory_options opts, error_code& ec)
|
||||
: __stream_(INVALID_HANDLE_VALUE), __root_(root) {
|
||||
__stream_ = ::FindFirstFileEx(root.c_str(), &__data_);
|
||||
if (__stream_ == INVALID_HANDLE_VALUE) {
|
||||
ec = error_code(::GetLastError(), generic_category());
|
||||
const bool ignore_permission_denied =
|
||||
bool(opts & directory_options::skip_permission_denied);
|
||||
if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED)
|
||||
ec.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~__dir_stream() noexcept {
|
||||
if (__stream_ == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
close();
|
||||
}
|
||||
|
||||
bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; }
|
||||
|
||||
bool advance(error_code& ec) {
|
||||
while (::FindNextFile(__stream_, &__data_)) {
|
||||
if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, ".."))
|
||||
continue;
|
||||
// FIXME: Cache more of this
|
||||
//directory_entry::__cached_data cdata;
|
||||
//cdata.__type_ = get_file_type(__data_);
|
||||
//cdata.__size_ = get_file_size(__data_);
|
||||
//cdata.__write_time_ = get_write_time(__data_);
|
||||
__entry_.__assign_iter_entry(
|
||||
__root_ / __data_.cFileName,
|
||||
directory_entry::__create_iter_result(get_file_type(__data)));
|
||||
return true;
|
||||
}
|
||||
ec = error_code(::GetLastError(), generic_category());
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
error_code close() noexcept {
|
||||
error_code ec;
|
||||
if (!::FindClose(__stream_))
|
||||
ec = error_code(::GetLastError(), generic_category());
|
||||
__stream_ = INVALID_HANDLE_VALUE;
|
||||
return ec;
|
||||
}
|
||||
|
||||
HANDLE __stream_{INVALID_HANDLE_VALUE};
|
||||
WIN32_FIND_DATA __data_;
|
||||
|
||||
public:
|
||||
path __root_;
|
||||
directory_entry __entry_;
|
||||
};
|
||||
#else
|
||||
class __dir_stream {
|
||||
public:
|
||||
__dir_stream() = delete;
|
||||
__dir_stream& operator=(const __dir_stream&) = delete;
|
||||
|
||||
__dir_stream(__dir_stream&& other) noexcept : __stream_(other.__stream_),
|
||||
__root_(move(other.__root_)),
|
||||
__entry_(move(other.__entry_)) {
|
||||
other.__stream_ = nullptr;
|
||||
}
|
||||
|
||||
__dir_stream(const path& root, directory_options opts, error_code& ec)
|
||||
: __stream_(nullptr), __root_(root) {
|
||||
if ((__stream_ = ::opendir(root.c_str())) == nullptr) {
|
||||
ec = detail::capture_errno();
|
||||
const bool allow_eacess =
|
||||
bool(opts & directory_options::skip_permission_denied);
|
||||
if (allow_eacess && ec.value() == EACCES)
|
||||
ec.clear();
|
||||
return;
|
||||
}
|
||||
advance(ec);
|
||||
}
|
||||
|
||||
~__dir_stream() noexcept {
|
||||
if (__stream_)
|
||||
close();
|
||||
}
|
||||
|
||||
bool good() const noexcept { return __stream_ != nullptr; }
|
||||
|
||||
bool advance(error_code& ec) {
|
||||
while (true) {
|
||||
auto str_type_pair = detail::posix_readdir(__stream_, ec);
|
||||
auto& str = str_type_pair.first;
|
||||
if (str == "." || str == "..") {
|
||||
continue;
|
||||
} else if (ec || str.empty()) {
|
||||
close();
|
||||
return false;
|
||||
} else {
|
||||
__entry_.__assign_iter_entry(
|
||||
__root_ / str,
|
||||
directory_entry::__create_iter_result(str_type_pair.second));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
error_code close() noexcept {
|
||||
error_code m_ec;
|
||||
if (::closedir(__stream_) == -1)
|
||||
m_ec = detail::capture_errno();
|
||||
__stream_ = nullptr;
|
||||
return m_ec;
|
||||
}
|
||||
|
||||
DIR* __stream_{nullptr};
|
||||
|
||||
public:
|
||||
path __root_;
|
||||
directory_entry __entry_;
|
||||
};
|
||||
#endif
|
||||
|
||||
// directory_iterator
|
||||
|
||||
directory_iterator::directory_iterator(const path& p, error_code* ec,
|
||||
directory_options opts) {
|
||||
ErrorHandler<void> err("directory_iterator::directory_iterator(...)", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
__imp_ = make_shared<__dir_stream>(p, opts, m_ec);
|
||||
if (ec)
|
||||
*ec = m_ec;
|
||||
if (!__imp_->good()) {
|
||||
__imp_.reset();
|
||||
if (m_ec)
|
||||
err.report(m_ec);
|
||||
}
|
||||
}
|
||||
|
||||
directory_iterator& directory_iterator::__increment(error_code* ec) {
|
||||
_LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator");
|
||||
ErrorHandler<void> err("directory_iterator::operator++()", ec);
|
||||
|
||||
error_code m_ec;
|
||||
if (!__imp_->advance(m_ec)) {
|
||||
path root = move(__imp_->__root_);
|
||||
__imp_.reset();
|
||||
if (m_ec)
|
||||
err.report(m_ec, "at root \"%s\"", root);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
directory_entry const& directory_iterator::__dereference() const {
|
||||
_LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator");
|
||||
return __imp_->__entry_;
|
||||
}
|
||||
|
||||
// recursive_directory_iterator
|
||||
|
||||
struct recursive_directory_iterator::__shared_imp {
|
||||
stack<__dir_stream> __stack_;
|
||||
directory_options __options_;
|
||||
};
|
||||
|
||||
recursive_directory_iterator::recursive_directory_iterator(
|
||||
const path& p, directory_options opt, error_code* ec)
|
||||
: __imp_(nullptr), __rec_(true) {
|
||||
ErrorHandler<void> err("recursive_directory_iterator", ec, &p);
|
||||
|
||||
error_code m_ec;
|
||||
__dir_stream new_s(p, opt, m_ec);
|
||||
if (m_ec)
|
||||
err.report(m_ec);
|
||||
if (m_ec || !new_s.good())
|
||||
return;
|
||||
|
||||
__imp_ = make_shared<__shared_imp>();
|
||||
__imp_->__options_ = opt;
|
||||
__imp_->__stack_.push(move(new_s));
|
||||
}
|
||||
|
||||
void recursive_directory_iterator::__pop(error_code* ec) {
|
||||
_LIBCPP_ASSERT(__imp_, "Popping the end iterator");
|
||||
if (ec)
|
||||
ec->clear();
|
||||
__imp_->__stack_.pop();
|
||||
if (__imp_->__stack_.size() == 0)
|
||||
__imp_.reset();
|
||||
else
|
||||
__advance(ec);
|
||||
}
|
||||
|
||||
directory_options recursive_directory_iterator::options() const {
|
||||
return __imp_->__options_;
|
||||
}
|
||||
|
||||
int recursive_directory_iterator::depth() const {
|
||||
return __imp_->__stack_.size() - 1;
|
||||
}
|
||||
|
||||
const directory_entry& recursive_directory_iterator::__dereference() const {
|
||||
return __imp_->__stack_.top().__entry_;
|
||||
}
|
||||
|
||||
recursive_directory_iterator&
|
||||
recursive_directory_iterator::__increment(error_code* ec) {
|
||||
if (ec)
|
||||
ec->clear();
|
||||
if (recursion_pending()) {
|
||||
if (__try_recursion(ec) || (ec && *ec))
|
||||
return *this;
|
||||
}
|
||||
__rec_ = true;
|
||||
__advance(ec);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void recursive_directory_iterator::__advance(error_code* ec) {
|
||||
ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
|
||||
|
||||
const directory_iterator end_it;
|
||||
auto& stack = __imp_->__stack_;
|
||||
error_code m_ec;
|
||||
while (stack.size() > 0) {
|
||||
if (stack.top().advance(m_ec))
|
||||
return;
|
||||
if (m_ec)
|
||||
break;
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
if (m_ec) {
|
||||
path root = move(stack.top().__root_);
|
||||
__imp_.reset();
|
||||
err.report(m_ec, "at root \"%s\"", root);
|
||||
} else {
|
||||
__imp_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool recursive_directory_iterator::__try_recursion(error_code* ec) {
|
||||
ErrorHandler<void> err("recursive_directory_iterator::operator++()", ec);
|
||||
|
||||
bool rec_sym = bool(options() & directory_options::follow_directory_symlink);
|
||||
|
||||
auto& curr_it = __imp_->__stack_.top();
|
||||
|
||||
bool skip_rec = false;
|
||||
error_code m_ec;
|
||||
if (!rec_sym) {
|
||||
file_status st(curr_it.__entry_.__get_sym_ft(&m_ec));
|
||||
if (m_ec && status_known(st))
|
||||
m_ec.clear();
|
||||
if (m_ec || is_symlink(st) || !is_directory(st))
|
||||
skip_rec = true;
|
||||
} else {
|
||||
file_status st(curr_it.__entry_.__get_ft(&m_ec));
|
||||
if (m_ec && status_known(st))
|
||||
m_ec.clear();
|
||||
if (m_ec || !is_directory(st))
|
||||
skip_rec = true;
|
||||
}
|
||||
|
||||
if (!skip_rec) {
|
||||
__dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec);
|
||||
if (new_it.good()) {
|
||||
__imp_->__stack_.push(move(new_it));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (m_ec) {
|
||||
const bool allow_eacess =
|
||||
bool(__imp_->__options_ & directory_options::skip_permission_denied);
|
||||
if (m_ec.value() == EACCES && allow_eacess) {
|
||||
if (ec)
|
||||
ec->clear();
|
||||
} else {
|
||||
path at_ent = move(curr_it.__entry_.__p_);
|
||||
__imp_.reset();
|
||||
err.report(m_ec, "attempting recursion into \"%s\"", at_ent);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::filesystem
|
||||
/* clang-format on */
|
||||
@@ -0,0 +1,441 @@
|
||||
//===----------------------------------------------------------------------===////
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is dual licensed under the MIT and the University of Illinois Open
|
||||
// Source Licenses. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===////
|
||||
/* clang-format off */
|
||||
#ifndef AUTO_FILESYSTEM_COMMON_H
|
||||
#define AUTO_FILESYSTEM_COMMON_H
|
||||
|
||||
#include "automotive/filesystem"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <climits>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/time.h> // for ::utimes as used in __last_write_time
|
||||
#include <fcntl.h> /* values for fchmodat */
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
// We can use the presence of UTIME_OMIT to detect platforms that provide
|
||||
// utimensat.
|
||||
#if defined(UTIME_OMIT)
|
||||
#define _LIBCPP_USE_UTIMENSAT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
#endif
|
||||
|
||||
namespace android::hardware::automotive::filesystem {
|
||||
using namespace std::chrono;
|
||||
|
||||
using std::error_code;
|
||||
using std::is_floating_point;
|
||||
using std::micro;
|
||||
using std::nano;
|
||||
using std::ratio;
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
static string format_string_imp(const char* msg, ...) {
|
||||
// we might need a second shot at this, so pre-emptivly make a copy
|
||||
struct GuardVAList {
|
||||
va_list& target;
|
||||
bool active = true;
|
||||
GuardVAList(va_list& target) : target(target), active(true) {}
|
||||
void clear() {
|
||||
if (active)
|
||||
va_end(target);
|
||||
active = false;
|
||||
}
|
||||
~GuardVAList() {
|
||||
if (active)
|
||||
va_end(target);
|
||||
}
|
||||
};
|
||||
va_list args;
|
||||
va_start(args, msg);
|
||||
GuardVAList args_guard(args);
|
||||
|
||||
va_list args_cp;
|
||||
va_copy(args_cp, args);
|
||||
GuardVAList args_copy_guard(args_cp);
|
||||
|
||||
std::string result;
|
||||
|
||||
array<char, 256> local_buff;
|
||||
size_t size_with_null = local_buff.size();
|
||||
auto ret = ::vsnprintf(local_buff.data(), size_with_null, msg, args_cp);
|
||||
|
||||
args_copy_guard.clear();
|
||||
|
||||
// handle empty expansion
|
||||
if (ret == 0)
|
||||
return result;
|
||||
if (static_cast<size_t>(ret) < size_with_null) {
|
||||
result.assign(local_buff.data(), static_cast<size_t>(ret));
|
||||
return result;
|
||||
}
|
||||
|
||||
// we did not provide a long enough buffer on our first attempt. The
|
||||
// return value is the number of bytes (excluding the null byte) that are
|
||||
// needed for formatting.
|
||||
size_with_null = static_cast<size_t>(ret) + 1;
|
||||
result.__resize_default_init(size_with_null - 1);
|
||||
ret = ::vsnprintf(&result[0], size_with_null, msg, args);
|
||||
_LIBCPP_ASSERT(static_cast<size_t>(ret) == (size_with_null - 1), "TODO");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* unwrap(string const& s) { return s.c_str(); }
|
||||
const char* unwrap(path const& p) { return p.native().c_str(); }
|
||||
template <class Arg>
|
||||
Arg const& unwrap(Arg const& a) {
|
||||
static_assert(!is_class<Arg>::value, "cannot pass class here");
|
||||
return a;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
string format_string(const char* fmt, Args const&... args) {
|
||||
return format_string_imp(fmt, unwrap(args)...);
|
||||
}
|
||||
|
||||
error_code capture_errno() {
|
||||
_LIBCPP_ASSERT(errno, "Expected errno to be non-zero");
|
||||
return error_code(errno, generic_category());
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T error_value();
|
||||
template <>
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX11 void error_value<void>() {}
|
||||
template <>
|
||||
bool error_value<bool>() {
|
||||
return false;
|
||||
}
|
||||
template <>
|
||||
uintmax_t error_value<uintmax_t>() {
|
||||
return uintmax_t(-1);
|
||||
}
|
||||
template <>
|
||||
_LIBCPP_CONSTEXPR_AFTER_CXX11 file_time_type error_value<file_time_type>() {
|
||||
return file_time_type::min();
|
||||
}
|
||||
template <>
|
||||
path error_value<path>() {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct ErrorHandler {
|
||||
const char* func_name;
|
||||
error_code* ec = nullptr;
|
||||
const path* p1 = nullptr;
|
||||
const path* p2 = nullptr;
|
||||
|
||||
ErrorHandler(const char* fname, error_code* ec, const path* p1 = nullptr,
|
||||
const path* p2 = nullptr)
|
||||
: func_name(fname), ec(ec), p1(p1), p2(p2) {
|
||||
if (ec)
|
||||
ec->clear();
|
||||
}
|
||||
|
||||
T report(const error_code& m_ec) const {
|
||||
if (ec) {
|
||||
*ec = m_ec;
|
||||
return error_value<T>();
|
||||
}
|
||||
string what = string("in ") + func_name;
|
||||
switch (bool(p1) + bool(p2)) {
|
||||
case 0:
|
||||
__throw_filesystem_error(what, m_ec);
|
||||
case 1:
|
||||
__throw_filesystem_error(what, *p1, m_ec);
|
||||
case 2:
|
||||
__throw_filesystem_error(what, *p1, *p2, m_ec);
|
||||
}
|
||||
_LIBCPP_UNREACHABLE();
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
T report(const error_code& m_ec, const char* msg, Args const&... args) const {
|
||||
if (ec) {
|
||||
*ec = m_ec;
|
||||
return error_value<T>();
|
||||
}
|
||||
string what =
|
||||
string("in ") + func_name + ": " + format_string(msg, args...);
|
||||
switch (bool(p1) + bool(p2)) {
|
||||
case 0:
|
||||
__throw_filesystem_error(what, m_ec);
|
||||
case 1:
|
||||
__throw_filesystem_error(what, *p1, m_ec);
|
||||
case 2:
|
||||
__throw_filesystem_error(what, *p1, *p2, m_ec);
|
||||
}
|
||||
_LIBCPP_UNREACHABLE();
|
||||
}
|
||||
|
||||
T report(errc const& err) const { return report(make_error_code(err)); }
|
||||
|
||||
template <class... Args>
|
||||
T report(errc const& err, const char* msg, Args const&... args) const {
|
||||
return report(make_error_code(err), msg, args...);
|
||||
}
|
||||
|
||||
private:
|
||||
ErrorHandler(ErrorHandler const&) = delete;
|
||||
ErrorHandler& operator=(ErrorHandler const&) = delete;
|
||||
};
|
||||
|
||||
using chrono::duration;
|
||||
using chrono::duration_cast;
|
||||
|
||||
using TimeSpec = struct ::timespec;
|
||||
using StatT = struct ::stat;
|
||||
|
||||
template <class FileTimeT, class TimeT,
|
||||
bool IsFloat = is_floating_point<typename FileTimeT::rep>::value>
|
||||
struct time_util_base {
|
||||
using rep = typename FileTimeT::rep;
|
||||
using fs_duration = typename FileTimeT::duration;
|
||||
using fs_seconds = duration<rep>;
|
||||
using fs_nanoseconds = duration<rep, nano>;
|
||||
using fs_microseconds = duration<rep, micro>;
|
||||
|
||||
static constexpr rep max_seconds =
|
||||
duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
|
||||
|
||||
static constexpr rep max_nsec =
|
||||
duration_cast<fs_nanoseconds>(FileTimeT::duration::max() -
|
||||
fs_seconds(max_seconds))
|
||||
.count();
|
||||
|
||||
static constexpr rep min_seconds =
|
||||
duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
|
||||
|
||||
static constexpr rep min_nsec_timespec =
|
||||
duration_cast<fs_nanoseconds>(
|
||||
(FileTimeT::duration::min() - fs_seconds(min_seconds)) +
|
||||
fs_seconds(1))
|
||||
.count();
|
||||
|
||||
private:
|
||||
#if _LIBCPP_STD_VER > 11 && !defined(_LIBCPP_HAS_NO_CXX14_CONSTEXPR)
|
||||
static constexpr fs_duration get_min_nsecs() {
|
||||
return duration_cast<fs_duration>(
|
||||
fs_nanoseconds(min_nsec_timespec) -
|
||||
duration_cast<fs_nanoseconds>(fs_seconds(1)));
|
||||
}
|
||||
// Static assert that these values properly round trip.
|
||||
static_assert(fs_seconds(min_seconds) + get_min_nsecs() ==
|
||||
FileTimeT::duration::min(),
|
||||
"value doesn't roundtrip");
|
||||
|
||||
static constexpr bool check_range() {
|
||||
// This kinda sucks, but it's what happens when we don't have __int128_t.
|
||||
if (sizeof(TimeT) == sizeof(rep)) {
|
||||
typedef duration<long long, ratio<3600 * 24 * 365> > Years;
|
||||
return duration_cast<Years>(fs_seconds(max_seconds)) > Years(250) &&
|
||||
duration_cast<Years>(fs_seconds(min_seconds)) < Years(-250);
|
||||
}
|
||||
return max_seconds >= numeric_limits<TimeT>::max() &&
|
||||
min_seconds <= numeric_limits<TimeT>::min();
|
||||
}
|
||||
static_assert(check_range(), "the representable range is unacceptable small");
|
||||
#endif
|
||||
};
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
struct time_util_base<FileTimeT, TimeT, true> {
|
||||
using rep = typename FileTimeT::rep;
|
||||
using fs_duration = typename FileTimeT::duration;
|
||||
using fs_seconds = duration<rep>;
|
||||
using fs_nanoseconds = duration<rep, nano>;
|
||||
using fs_microseconds = duration<rep, micro>;
|
||||
|
||||
static const rep max_seconds;
|
||||
static const rep max_nsec;
|
||||
static const rep min_seconds;
|
||||
static const rep min_nsec_timespec;
|
||||
};
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep
|
||||
time_util_base<FileTimeT, TimeT, true>::max_seconds =
|
||||
duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep time_util_base<FileTimeT, TimeT, true>::max_nsec =
|
||||
duration_cast<fs_nanoseconds>(FileTimeT::duration::max() -
|
||||
fs_seconds(max_seconds))
|
||||
.count();
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep
|
||||
time_util_base<FileTimeT, TimeT, true>::min_seconds =
|
||||
duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
|
||||
|
||||
template <class FileTimeT, class TimeT>
|
||||
const typename FileTimeT::rep
|
||||
time_util_base<FileTimeT, TimeT, true>::min_nsec_timespec =
|
||||
duration_cast<fs_nanoseconds>((FileTimeT::duration::min() -
|
||||
fs_seconds(min_seconds)) +
|
||||
fs_seconds(1))
|
||||
.count();
|
||||
|
||||
template <class FileTimeT, class TimeT, class TimeSpecT>
|
||||
struct time_util : time_util_base<FileTimeT, TimeT> {
|
||||
using Base = time_util_base<FileTimeT, TimeT>;
|
||||
using Base::max_nsec;
|
||||
using Base::max_seconds;
|
||||
using Base::min_nsec_timespec;
|
||||
using Base::min_seconds;
|
||||
|
||||
using typename Base::fs_duration;
|
||||
using typename Base::fs_microseconds;
|
||||
using typename Base::fs_nanoseconds;
|
||||
using typename Base::fs_seconds;
|
||||
|
||||
public:
|
||||
template <class CType, class ChronoType>
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool checked_set(CType* out,
|
||||
ChronoType time) {
|
||||
using Lim = numeric_limits<CType>;
|
||||
if (time > Lim::max() || time < Lim::min())
|
||||
return false;
|
||||
*out = static_cast<CType>(time);
|
||||
return true;
|
||||
}
|
||||
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) {
|
||||
if (tm.tv_sec >= 0) {
|
||||
return tm.tv_sec < max_seconds ||
|
||||
(tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec);
|
||||
} else if (tm.tv_sec == (min_seconds - 1)) {
|
||||
return tm.tv_nsec >= min_nsec_timespec;
|
||||
} else {
|
||||
return tm.tv_sec >= min_seconds;
|
||||
}
|
||||
}
|
||||
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) {
|
||||
auto secs = duration_cast<fs_seconds>(tm.time_since_epoch());
|
||||
auto nsecs = duration_cast<fs_nanoseconds>(tm.time_since_epoch() - secs);
|
||||
if (nsecs.count() < 0) {
|
||||
secs = secs + fs_seconds(1);
|
||||
nsecs = nsecs + fs_seconds(1);
|
||||
}
|
||||
using TLim = numeric_limits<TimeT>;
|
||||
if (secs.count() >= 0)
|
||||
return secs.count() <= TLim::max();
|
||||
return secs.count() >= TLim::min();
|
||||
}
|
||||
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 FileTimeT
|
||||
convert_from_timespec(TimeSpecT tm) {
|
||||
if (tm.tv_sec >= 0 || tm.tv_nsec == 0) {
|
||||
return FileTimeT(fs_seconds(tm.tv_sec) +
|
||||
duration_cast<fs_duration>(fs_nanoseconds(tm.tv_nsec)));
|
||||
} else { // tm.tv_sec < 0
|
||||
auto adj_subsec = duration_cast<fs_duration>(fs_seconds(1) -
|
||||
fs_nanoseconds(tm.tv_nsec));
|
||||
auto Dur = fs_seconds(tm.tv_sec + 1) - adj_subsec;
|
||||
return FileTimeT(Dur);
|
||||
}
|
||||
}
|
||||
|
||||
template <class SubSecT>
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool
|
||||
set_times_checked(TimeT* sec_out, SubSecT* subsec_out, FileTimeT tp) {
|
||||
auto dur = tp.time_since_epoch();
|
||||
auto sec_dur = duration_cast<fs_seconds>(dur);
|
||||
auto subsec_dur = duration_cast<fs_nanoseconds>(dur - sec_dur);
|
||||
// The tv_nsec and tv_usec fields must not be negative so adjust accordingly
|
||||
if (subsec_dur.count() < 0) {
|
||||
if (sec_dur.count() > min_seconds) {
|
||||
sec_dur = sec_dur - fs_seconds(1);
|
||||
subsec_dur = subsec_dur + fs_seconds(1);
|
||||
} else {
|
||||
subsec_dur = fs_nanoseconds::zero();
|
||||
}
|
||||
}
|
||||
return checked_set(sec_out, sec_dur.count()) &&
|
||||
checked_set(subsec_out, subsec_dur.count());
|
||||
}
|
||||
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool convert_to_timespec(TimeSpecT& dest,
|
||||
FileTimeT tp) {
|
||||
if (!is_representable(tp))
|
||||
return false;
|
||||
return set_times_checked(&dest.tv_sec, &dest.tv_nsec, tp);
|
||||
}
|
||||
};
|
||||
|
||||
using fs_time = time_util<file_time_type, time_t, TimeSpec>;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; }
|
||||
TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; }
|
||||
#else
|
||||
TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; }
|
||||
TimeSpec extract_atime(StatT const& st) { return st.st_atim; }
|
||||
#endif
|
||||
|
||||
// allow the utimes implementation to compile even it we're not going
|
||||
// to use it.
|
||||
|
||||
bool posix_utimes(const path& p, std::array<TimeSpec, 2> const& TS,
|
||||
error_code& ec) {
|
||||
using namespace chrono;
|
||||
auto Convert = [](long nsec) {
|
||||
using int_type = decltype(std::declval< ::timeval>().tv_usec);
|
||||
auto dur = duration_cast<microseconds>(nanoseconds(nsec)).count();
|
||||
return static_cast<int_type>(dur);
|
||||
};
|
||||
struct ::timeval ConvertedTS[2] = {{TS[0].tv_sec, Convert(TS[0].tv_nsec)},
|
||||
{TS[1].tv_sec, Convert(TS[1].tv_nsec)}};
|
||||
if (::utimes(p.c_str(), ConvertedTS) == -1) {
|
||||
ec = capture_errno();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(_LIBCPP_USE_UTIMENSAT)
|
||||
bool posix_utimensat(const path& p, std::array<TimeSpec, 2> const& TS,
|
||||
error_code& ec) {
|
||||
if (::utimensat(AT_FDCWD, p.c_str(), TS.data(), 0) == -1) {
|
||||
ec = capture_errno();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool set_file_times(const path& p, std::array<TimeSpec, 2> const& TS,
|
||||
error_code& ec) {
|
||||
#if !defined(_LIBCPP_USE_UTIMENSAT)
|
||||
return posix_utimes(p, TS, ec);
|
||||
#else
|
||||
return posix_utimensat(p, TS, ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // end namespace detail
|
||||
|
||||
} // namespace android::hardware::automotive::filesystem
|
||||
|
||||
#endif // AUTO_FILESYSTEM_COMMON_H
|
||||
/* clang-format on */
|
||||
1773
automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp
Normal file
1773
automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp
Normal file
File diff suppressed because it is too large
Load Diff
30
automotive/can/1.0/default/libnetdevice/Android.bp
Normal file
30
automotive/can/1.0/default/libnetdevice/Android.bp
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
cc_library_static {
|
||||
name: "android.hardware.automotive.can@libnetdevice",
|
||||
defaults: ["android.hardware.automotive.can@defaults"],
|
||||
vendor_available: true,
|
||||
relative_install_path: "hw",
|
||||
srcs: [
|
||||
"NetlinkRequest.cpp",
|
||||
"NetlinkSocket.cpp",
|
||||
"can.cpp",
|
||||
"common.cpp",
|
||||
"libnetdevice.cpp",
|
||||
],
|
||||
export_include_dirs: ["include"],
|
||||
}
|
||||
54
automotive/can/1.0/default/libnetdevice/NetlinkRequest.cpp
Normal file
54
automotive/can/1.0/default/libnetdevice/NetlinkRequest.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "NetlinkRequest.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
namespace android::netdevice::impl {
|
||||
|
||||
static struct rtattr* nlmsg_tail(struct nlmsghdr* n) {
|
||||
return reinterpret_cast<struct rtattr*>( //
|
||||
reinterpret_cast<uintptr_t>(n) + NLMSG_ALIGN(n->nlmsg_len));
|
||||
}
|
||||
|
||||
struct rtattr* addattr_l(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type, const void* data,
|
||||
size_t dataLen) {
|
||||
size_t newLen = NLMSG_ALIGN(n->nlmsg_len) + RTA_SPACE(dataLen);
|
||||
if (newLen > maxLen) {
|
||||
LOG(ERROR) << "addattr_l failed - exceeded maxLen: " << newLen << " > " << maxLen;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto attr = nlmsg_tail(n);
|
||||
attr->rta_len = RTA_SPACE(dataLen);
|
||||
attr->rta_type = type;
|
||||
if (dataLen > 0) memcpy(RTA_DATA(attr), data, dataLen);
|
||||
|
||||
n->nlmsg_len = newLen;
|
||||
return attr;
|
||||
}
|
||||
|
||||
struct rtattr* addattr_nest(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type) {
|
||||
return addattr_l(n, maxLen, type, nullptr, 0);
|
||||
}
|
||||
|
||||
void addattr_nest_end(struct nlmsghdr* n, struct rtattr* nest) {
|
||||
size_t nestLen = reinterpret_cast<uintptr_t>(nlmsg_tail(n)) - reinterpret_cast<uintptr_t>(nest);
|
||||
nest->rta_len = nestLen;
|
||||
}
|
||||
|
||||
} // namespace android::netdevice::impl
|
||||
153
automotive/can/1.0/default/libnetdevice/NetlinkRequest.h
Normal file
153
automotive/can/1.0/default/libnetdevice/NetlinkRequest.h
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace android::netdevice {
|
||||
|
||||
typedef unsigned short rtattrtype_t; // as in rtnetlink.h
|
||||
typedef __u16 nlmsgtype_t; // as in netlink.h
|
||||
|
||||
/** Implementation details, do not use outside NetlinkRequest template. */
|
||||
namespace impl {
|
||||
|
||||
struct rtattr* addattr_l(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type, const void* data,
|
||||
size_t dataLen);
|
||||
struct rtattr* addattr_nest(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type);
|
||||
void addattr_nest_end(struct nlmsghdr* n, struct rtattr* nest);
|
||||
|
||||
} // namespace impl
|
||||
|
||||
/**
|
||||
* Wrapper around NETLINK_ROUTE messages, to build them in C++ style.
|
||||
*
|
||||
* \param T specific message header (such as struct ifinfomsg)
|
||||
* \param BUFSIZE how much space to reserve for payload (not counting the header size)
|
||||
*/
|
||||
template <class T, unsigned int BUFSIZE = 128>
|
||||
struct NetlinkRequest {
|
||||
/**
|
||||
* Create empty message.
|
||||
*
|
||||
* \param type Message type (such as RTM_NEWLINK)
|
||||
* \param flags Message flags (such as NLM_F_REQUEST)
|
||||
*/
|
||||
NetlinkRequest(nlmsgtype_t type, uint16_t flags) {
|
||||
mRequest.nlmsg.nlmsg_len = NLMSG_LENGTH(sizeof(mRequest.data));
|
||||
mRequest.nlmsg.nlmsg_type = type;
|
||||
mRequest.nlmsg.nlmsg_flags = flags;
|
||||
}
|
||||
|
||||
/** \return pointer to raw netlink message header. */
|
||||
struct nlmsghdr* header() {
|
||||
return &mRequest.nlmsg;
|
||||
}
|
||||
/** Reference to message-specific header. */
|
||||
T& data() { return mRequest.data; }
|
||||
|
||||
/**
|
||||
* Adds an attribute of a simple type.
|
||||
*
|
||||
* If this method fails (i.e. due to insufficient space), the message will be marked
|
||||
* as bad (\see isGood).
|
||||
*
|
||||
* \param type attribute type (such as IFLA_IFNAME)
|
||||
* \param attr attribute data
|
||||
*/
|
||||
template <class A>
|
||||
void addattr(rtattrtype_t type, const A& attr) {
|
||||
if (!mIsGood) return;
|
||||
auto ap = impl::addattr_l(&mRequest.nlmsg, sizeof(mRequest), type, &attr, sizeof(attr));
|
||||
if (ap == nullptr) mIsGood = false;
|
||||
}
|
||||
|
||||
template <>
|
||||
void addattr(rtattrtype_t type, const std::string& s) {
|
||||
if (!mIsGood) return;
|
||||
auto ap = impl::addattr_l(&mRequest.nlmsg, sizeof(mRequest), type, s.c_str(), s.size() + 1);
|
||||
if (ap == nullptr) mIsGood = false;
|
||||
}
|
||||
|
||||
/** Guard class to frame nested attributes. See nest(int). */
|
||||
struct Nest {
|
||||
Nest(NetlinkRequest& req, rtattrtype_t type) : mReq(req), mAttr(req.nestStart(type)) {}
|
||||
~Nest() { mReq.nestEnd(mAttr); }
|
||||
|
||||
private:
|
||||
NetlinkRequest& mReq;
|
||||
struct rtattr* mAttr;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Nest);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add nested attribute.
|
||||
*
|
||||
* The returned object is a guard for auto-nesting children inside the argument attribute.
|
||||
* When the Nest object goes out of scope, the nesting attribute is closed.
|
||||
*
|
||||
* Example usage nesting IFLA_CAN_BITTIMING inside IFLA_INFO_DATA, which is nested
|
||||
* inside IFLA_LINKINFO:
|
||||
* NetlinkRequest<struct ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST);
|
||||
* {
|
||||
* auto linkinfo = req.nest(IFLA_LINKINFO);
|
||||
* req.addattr(IFLA_INFO_KIND, "can");
|
||||
* {
|
||||
* auto infodata = req.nest(IFLA_INFO_DATA);
|
||||
* req.addattr(IFLA_CAN_BITTIMING, bitTimingStruct);
|
||||
* }
|
||||
* }
|
||||
* // use req
|
||||
*
|
||||
* \param type attribute type (such as IFLA_LINKINFO)
|
||||
*/
|
||||
Nest nest(int type) { return Nest(*this, type); }
|
||||
|
||||
/**
|
||||
* Indicates, whether the message is in a good state.
|
||||
*
|
||||
* The bad state is usually a result of payload buffer being too small.
|
||||
* You can modify BUFSIZE template parameter to fix this.
|
||||
*/
|
||||
bool isGood() const { return mIsGood; }
|
||||
|
||||
private:
|
||||
bool mIsGood = true;
|
||||
|
||||
struct {
|
||||
struct nlmsghdr nlmsg;
|
||||
T data;
|
||||
char buf[BUFSIZE];
|
||||
} mRequest = {};
|
||||
|
||||
struct rtattr* nestStart(rtattrtype_t type) {
|
||||
if (!mIsGood) return nullptr;
|
||||
auto attr = impl::addattr_nest(&mRequest.nlmsg, sizeof(mRequest), type);
|
||||
if (attr == nullptr) mIsGood = false;
|
||||
return attr;
|
||||
}
|
||||
|
||||
void nestEnd(struct rtattr* nest) {
|
||||
if (mIsGood && nest != nullptr) impl::addattr_nest_end(&mRequest.nlmsg, nest);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace android::netdevice
|
||||
112
automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp
Normal file
112
automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "NetlinkSocket.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
namespace android::netdevice {
|
||||
|
||||
NetlinkSocket::NetlinkSocket(int protocol) {
|
||||
mFd.reset(socket(AF_NETLINK, SOCK_RAW, protocol));
|
||||
if (!mFd.ok()) {
|
||||
PLOG(ERROR) << "Can't open Netlink socket";
|
||||
mFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_nl sa = {};
|
||||
sa.nl_family = AF_NETLINK;
|
||||
|
||||
if (bind(mFd.get(), reinterpret_cast<struct sockaddr*>(&sa), sizeof(sa)) < 0) {
|
||||
PLOG(ERROR) << "Can't bind Netlink socket";
|
||||
mFd.reset();
|
||||
mFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetlinkSocket::send(struct nlmsghdr* nlmsg) {
|
||||
if (mFailed) return false;
|
||||
|
||||
nlmsg->nlmsg_pid = 0; // kernel
|
||||
nlmsg->nlmsg_seq = mSeq++;
|
||||
nlmsg->nlmsg_flags |= NLM_F_ACK;
|
||||
|
||||
struct iovec iov = {nlmsg, nlmsg->nlmsg_len};
|
||||
|
||||
struct sockaddr_nl sa = {};
|
||||
sa.nl_family = AF_NETLINK;
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_name = &sa;
|
||||
msg.msg_namelen = sizeof(sa);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
if (sendmsg(mFd.get(), &msg, 0) < 0) {
|
||||
PLOG(ERROR) << "Can't send Netlink message";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetlinkSocket::receiveAck() {
|
||||
if (mFailed) return false;
|
||||
|
||||
char buf[8192];
|
||||
|
||||
struct sockaddr_nl sa;
|
||||
struct iovec iov = {buf, sizeof(buf)};
|
||||
|
||||
struct msghdr msg = {};
|
||||
msg.msg_name = &sa;
|
||||
msg.msg_namelen = sizeof(sa);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
const ssize_t status = recvmsg(mFd.get(), &msg, 0);
|
||||
if (status < 0) {
|
||||
PLOG(ERROR) << "Failed to receive Netlink message";
|
||||
return false;
|
||||
}
|
||||
size_t remainingLen = status;
|
||||
|
||||
if (msg.msg_flags & MSG_TRUNC) {
|
||||
LOG(ERROR) << "Failed to receive Netlink message: truncated";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto nlmsg = reinterpret_cast<struct nlmsghdr*>(buf); NLMSG_OK(nlmsg, remainingLen);
|
||||
nlmsg = NLMSG_NEXT(nlmsg, remainingLen)) {
|
||||
// We're looking for error/ack message only, ignoring others.
|
||||
if (nlmsg->nlmsg_type != NLMSG_ERROR) {
|
||||
LOG(WARNING) << "Received unexpected Netlink message (ignored): " << nlmsg->nlmsg_type;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found error/ack message, return status.
|
||||
auto nlerr = reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(nlmsg));
|
||||
if (nlerr->error != 0) {
|
||||
LOG(ERROR) << "Received Netlink error message: " << nlerr->error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Couldn't find any error/ack messages.
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace android::netdevice
|
||||
66
automotive/can/1.0/default/libnetdevice/NetlinkSocket.h
Normal file
66
automotive/can/1.0/default/libnetdevice/NetlinkSocket.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NetlinkRequest.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include <linux/netlink.h>
|
||||
|
||||
namespace android::netdevice {
|
||||
|
||||
/**
|
||||
* A wrapper around AF_NETLINK sockets.
|
||||
*
|
||||
* This class is not thread safe to use a single instance between multiple threads, but it's fine to
|
||||
* use multiple instances over multiple threads.
|
||||
*/
|
||||
struct NetlinkSocket {
|
||||
NetlinkSocket(int protocol);
|
||||
|
||||
/**
|
||||
* Send Netlink message to Kernel.
|
||||
*
|
||||
* \param msg Message to send, nlmsg_seq will be set to next sequence number
|
||||
* \return true, if succeeded
|
||||
*/
|
||||
template <class T, unsigned int BUFSIZE>
|
||||
bool send(NetlinkRequest<T, BUFSIZE>& req) {
|
||||
if (!req.isGood()) return false;
|
||||
return send(req.header());
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive Netlink ACK message from Kernel.
|
||||
*
|
||||
* \return true if received ACK message, false in case of error
|
||||
*/
|
||||
bool receiveAck();
|
||||
|
||||
private:
|
||||
uint32_t mSeq = 0;
|
||||
base::unique_fd mFd;
|
||||
bool mFailed = false;
|
||||
|
||||
bool send(struct nlmsghdr* msg);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NetlinkSocket);
|
||||
};
|
||||
|
||||
} // namespace android::netdevice
|
||||
96
automotive/can/1.0/default/libnetdevice/can.cpp
Normal file
96
automotive/can/1.0/default/libnetdevice/can.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <libnetdevice/libnetdevice.h>
|
||||
|
||||
#include "NetlinkRequest.h"
|
||||
#include "NetlinkSocket.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/error.h>
|
||||
#include <linux/can/netlink.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
namespace android::netdevice::can {
|
||||
|
||||
static constexpr can_err_mask_t kErrMask = CAN_ERR_MASK;
|
||||
|
||||
base::unique_fd socket(const std::string& ifname) {
|
||||
struct sockaddr_can addr = {};
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = nametoindex(ifname);
|
||||
if (addr.can_ifindex == 0) {
|
||||
LOG(ERROR) << "Interface " << ifname << " doesn't exists";
|
||||
return {};
|
||||
}
|
||||
|
||||
base::unique_fd sock(::socket(PF_CAN, SOCK_RAW, CAN_RAW));
|
||||
if (!sock.ok()) {
|
||||
LOG(ERROR) << "Failed to create CAN socket";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (setsockopt(sock.get(), SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &kErrMask, sizeof(kErrMask)) < 0) {
|
||||
PLOG(ERROR) << "Can't receive error frames, CAN setsockpt failed";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (0 != fcntl(sock.get(), F_SETFL, O_RDWR | O_NONBLOCK)) {
|
||||
LOG(ERROR) << "Couldn't put CAN socket in non-blocking mode";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (0 != bind(sock.get(), reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr))) {
|
||||
LOG(ERROR) << "Can't bind to CAN interface " << ifname;
|
||||
return {};
|
||||
}
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
bool setBitrate(std::string ifname, uint32_t bitrate) {
|
||||
struct can_bittiming bt = {};
|
||||
bt.bitrate = bitrate;
|
||||
|
||||
NetlinkRequest<struct ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST);
|
||||
|
||||
const auto ifidx = nametoindex(ifname);
|
||||
if (ifidx == 0) {
|
||||
LOG(ERROR) << "Can't find interface " << ifname;
|
||||
return false;
|
||||
}
|
||||
req.data().ifi_index = ifidx;
|
||||
|
||||
{
|
||||
auto linkinfo = req.nest(IFLA_LINKINFO);
|
||||
req.addattr(IFLA_INFO_KIND, "can");
|
||||
{
|
||||
auto infodata = req.nest(IFLA_INFO_DATA);
|
||||
/* For CAN FD, it would require to add IFLA_CAN_DATA_BITTIMING
|
||||
* and IFLA_CAN_CTRLMODE as well. */
|
||||
req.addattr(IFLA_CAN_BITTIMING, bt);
|
||||
}
|
||||
}
|
||||
|
||||
NetlinkSocket sock(NETLINK_ROUTE);
|
||||
return sock.send(req) && sock.receiveAck();
|
||||
}
|
||||
|
||||
} // namespace android::netdevice::can
|
||||
36
automotive/can/1.0/default/libnetdevice/common.cpp
Normal file
36
automotive/can/1.0/default/libnetdevice/common.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include <net/if.h>
|
||||
|
||||
namespace android::netdevice {
|
||||
|
||||
unsigned int nametoindex(const std::string& ifname) {
|
||||
const auto ifidx = if_nametoindex(ifname.c_str());
|
||||
if (ifidx != 0) return ifidx;
|
||||
|
||||
const auto err = errno;
|
||||
if (err != ENODEV) {
|
||||
LOG(ERROR) << "if_nametoindex(" << ifname << ") failed: " << err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace android::netdevice
|
||||
34
automotive/can/1.0/default/libnetdevice/common.h
Normal file
34
automotive/can/1.0/default/libnetdevice/common.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace android::netdevice {
|
||||
|
||||
/**
|
||||
* Returns the index of a given network interface.
|
||||
*
|
||||
* If the syscall to check the index fails with other error than ENODEV, it gets logged and the
|
||||
* return value indicates the interface doesn't exists.
|
||||
*
|
||||
* \param ifname Interface to check
|
||||
* \return Interface index, or 0 if the interface doesn't exist
|
||||
*/
|
||||
unsigned int nametoindex(const std::string& ifname);
|
||||
|
||||
} // namespace android::netdevice
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace android::netdevice::can {
|
||||
|
||||
/**
|
||||
* Opens and binds SocketCAN socket.
|
||||
*
|
||||
* \param ifname Interface to open a socket against
|
||||
* \return Socket's FD or -1 in case of failure
|
||||
*/
|
||||
base::unique_fd socket(const std::string& ifname);
|
||||
|
||||
/**
|
||||
* Sets CAN interface bitrate.
|
||||
*
|
||||
* \param ifname Interface for which the bitrate is to be set
|
||||
* \return true on success, false on failure
|
||||
*/
|
||||
bool setBitrate(std::string ifname, uint32_t bitrate);
|
||||
|
||||
} // namespace android::netdevice::can
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace android::netdevice {
|
||||
|
||||
/**
|
||||
* Checks, if the network interface exists.
|
||||
*
|
||||
* \param ifname Interface to check
|
||||
* \return true if it exists, false otherwise
|
||||
*/
|
||||
bool exists(std::string ifname);
|
||||
|
||||
/**
|
||||
* Checks if network interface is up.
|
||||
*
|
||||
* \param ifname Interface to check
|
||||
* \return true/false if the check succeeded, nullopt otherwise
|
||||
*/
|
||||
std::optional<bool> isUp(std::string ifname);
|
||||
|
||||
/**
|
||||
* Brings network interface up.
|
||||
*
|
||||
* \param ifname Interface to bring up
|
||||
* \return true in case of success, false otherwise
|
||||
*/
|
||||
bool up(std::string ifname);
|
||||
|
||||
/**
|
||||
* Brings network interface down.
|
||||
*
|
||||
* \param ifname Interface to bring down
|
||||
* \return true in case of success, false otherwise
|
||||
*/
|
||||
bool down(std::string ifname);
|
||||
|
||||
/**
|
||||
* Adds virtual link.
|
||||
*
|
||||
* \param dev the name of the new virtual device
|
||||
* \param type the type of the new device
|
||||
* \return true in case of success, false otherwise
|
||||
*/
|
||||
bool add(std::string dev, std::string type);
|
||||
|
||||
/**
|
||||
* Deletes virtual link.
|
||||
*
|
||||
* \param dev the name of the device to remove
|
||||
* \return true in case of success, false otherwise
|
||||
*/
|
||||
bool del(std::string dev);
|
||||
|
||||
} // namespace android::netdevice
|
||||
98
automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
Normal file
98
automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <libnetdevice/libnetdevice.h>
|
||||
|
||||
#include "NetlinkRequest.h"
|
||||
#include "NetlinkSocket.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <net/if.h>
|
||||
|
||||
namespace android::netdevice {
|
||||
|
||||
bool exists(std::string ifname) {
|
||||
return nametoindex(ifname) != 0;
|
||||
}
|
||||
|
||||
static bool sendIfreq(unsigned long request, struct ifreq& ifr) {
|
||||
/* For general interfaces it would be socket(AF_INET, SOCK_DGRAM, 0),
|
||||
* but SEPolicy forces us to limit our flexibility here. */
|
||||
base::unique_fd sock(socket(PF_CAN, SOCK_RAW, CAN_RAW));
|
||||
if (!sock.ok()) {
|
||||
LOG(ERROR) << "Can't create socket";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ioctl(sock.get(), request, &ifr) < 0) {
|
||||
PLOG(ERROR) << "ioctl(" << std::hex << request << std::dec << ") failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct ifreq ifreqFromName(const std::string& ifname) {
|
||||
struct ifreq ifr = {};
|
||||
strlcpy(ifr.ifr_name, ifname.c_str(), IF_NAMESIZE);
|
||||
return ifr;
|
||||
}
|
||||
|
||||
std::optional<bool> isUp(std::string ifname) {
|
||||
struct ifreq ifr = ifreqFromName(ifname);
|
||||
if (!sendIfreq(SIOCGIFFLAGS, ifr)) return std::nullopt;
|
||||
return ifr.ifr_flags & IFF_UP;
|
||||
}
|
||||
|
||||
bool up(std::string ifname) {
|
||||
struct ifreq ifr = ifreqFromName(ifname);
|
||||
if (!sendIfreq(SIOCGIFFLAGS, ifr)) return false;
|
||||
ifr.ifr_flags |= IFF_UP;
|
||||
return sendIfreq(SIOCSIFFLAGS, ifr);
|
||||
}
|
||||
|
||||
bool down(std::string ifname) {
|
||||
struct ifreq ifr = ifreqFromName(ifname);
|
||||
if (!sendIfreq(SIOCGIFFLAGS, ifr)) return false;
|
||||
ifr.ifr_flags &= ~IFF_UP;
|
||||
return sendIfreq(SIOCSIFFLAGS, ifr);
|
||||
}
|
||||
|
||||
bool add(std::string dev, std::string type) {
|
||||
NetlinkRequest<struct ifinfomsg> req(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL);
|
||||
req.addattr(IFLA_IFNAME, dev);
|
||||
|
||||
{
|
||||
auto linkinfo = req.nest(IFLA_LINKINFO);
|
||||
req.addattr(IFLA_INFO_KIND, type);
|
||||
}
|
||||
|
||||
NetlinkSocket sock(NETLINK_ROUTE);
|
||||
return sock.send(req) && sock.receiveAck();
|
||||
}
|
||||
|
||||
bool del(std::string dev) {
|
||||
NetlinkRequest<struct ifinfomsg> req(RTM_DELLINK, NLM_F_REQUEST);
|
||||
req.addattr(IFLA_IFNAME, dev);
|
||||
|
||||
NetlinkSocket sock(NETLINK_ROUTE);
|
||||
return sock.send(req) && sock.receiveAck();
|
||||
}
|
||||
|
||||
} // namespace android::netdevice
|
||||
45
automotive/can/1.0/default/service.cpp
Normal file
45
automotive/can/1.0/default/service.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "CanController.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <hidl/HidlTransportSupport.h>
|
||||
|
||||
namespace android::hardware::automotive::can::V1_0::implementation {
|
||||
|
||||
static void canControllerService() {
|
||||
base::SetDefaultTag("CanController");
|
||||
base::SetMinimumLogSeverity(android::base::VERBOSE);
|
||||
configureRpcThreadpool(16, true);
|
||||
LOG(DEBUG) << "CAN controller service starting...";
|
||||
|
||||
sp<CanController> canController(new CanController);
|
||||
if (canController->registerAsService("socketcan") != OK) {
|
||||
LOG(FATAL) << "Failed to register CAN controller";
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(INFO) << "CAN controller service ready";
|
||||
joinRpcThreadpool();
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::V1_0::implementation
|
||||
|
||||
int main() {
|
||||
::android::hardware::automotive::can::V1_0::implementation::canControllerService();
|
||||
return 1; // canBusService (joinRpcThreadpool) shouldn't exit
|
||||
}
|
||||
21
automotive/can/1.0/hidl-utils/Android.bp
Normal file
21
automotive/can/1.0/hidl-utils/Android.bp
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
cc_library_headers {
|
||||
name: "android.hardware.automotive.can@hidl-utils-lib",
|
||||
export_include_dirs: ["include"],
|
||||
vendor_available: true,
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace android::hardware::automotive::hidl_utils {
|
||||
|
||||
/**
|
||||
* Helper functor to fetch results from multi-return HIDL calls.
|
||||
* It's meant to be used in place of _hidl_cb callbacks.
|
||||
*
|
||||
* Please note extracting these return variables outside of the callback scope requires making
|
||||
* a copy of each return variable. This may be costly for frequently called HIDL methods with
|
||||
* non-negligible return object size. Please be cautious about performance when using this.
|
||||
*
|
||||
* Example usage:
|
||||
* Result result;
|
||||
* sp<ISomeInterface> iface;
|
||||
* hidlObject->someMethod(arg1, arg2, hidl_utils::fill(&result, &iface)).assertOk();
|
||||
* // use result and iface
|
||||
*/
|
||||
template <typename... T>
|
||||
struct fill : public std::function<void(const T&...)> {
|
||||
/**
|
||||
* Create _hidl_cb functor that copies the call arguments to specified pointers.
|
||||
*
|
||||
* \param args... Targets to copy the call arguments to
|
||||
*/
|
||||
fill(T*... args) : mTargets(args...) {}
|
||||
|
||||
void operator()(const T&... args) { copy<0, T...>(args...); }
|
||||
|
||||
private:
|
||||
std::tuple<T*...> mTargets;
|
||||
|
||||
template <int Pos, typename First>
|
||||
inline void copy(const First& first) {
|
||||
*std::get<Pos>(mTargets) = first;
|
||||
}
|
||||
|
||||
template <int Pos, typename First, typename... Rest>
|
||||
inline void copy(const First& first, const Rest&... rest) {
|
||||
*std::get<Pos>(mTargets) = first;
|
||||
copy<Pos + 1, Rest...>(rest...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace android::hardware::automotive::hidl_utils
|
||||
66
automotive/can/1.0/tools/Android.bp
Normal file
66
automotive/can/1.0/tools/Android.bp
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright (C) 2019 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
cc_binary {
|
||||
name: "canhalctrl",
|
||||
defaults: ["android.hardware.automotive.can@defaults"],
|
||||
srcs: [
|
||||
"canhalctrl.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"android.hardware.automotive.can@1.0",
|
||||
"libhidlbase",
|
||||
],
|
||||
header_libs: [
|
||||
"android.hardware.automotive.can@hidl-utils-lib",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.automotive.can@libcanhaltools",
|
||||
],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "canhaldump",
|
||||
defaults: ["android.hardware.automotive.can@defaults"],
|
||||
srcs: [
|
||||
"canhaldump.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"android.hardware.automotive.can@1.0",
|
||||
"libhidlbase",
|
||||
],
|
||||
header_libs: [
|
||||
"android.hardware.automotive.can@hidl-utils-lib",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.automotive.can@libcanhaltools",
|
||||
],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
name: "canhalsend",
|
||||
defaults: ["android.hardware.automotive.can@defaults"],
|
||||
srcs: [
|
||||
"canhalsend.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"android.hardware.automotive.can@1.0",
|
||||
"libhidlbase",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.automotive.can@libcanhaltools",
|
||||
],
|
||||
}
|
||||
178
automotive/can/1.0/tools/canhalctrl.cpp
Normal file
178
automotive/can/1.0/tools/canhalctrl.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICanController.h>
|
||||
#include <android/hidl/manager/1.2/IServiceManager.h>
|
||||
#include <hidl-utils/hidl-utils.h>
|
||||
#include <libcanhaltools/libcanhaltools.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace android::hardware::automotive::can {
|
||||
|
||||
using ICanController = V1_0::ICanController;
|
||||
|
||||
static void usage() {
|
||||
std::cerr << "CAN bus HAL Control tool" << std::endl;
|
||||
std::cerr << std::endl << "usage:" << std::endl << std::endl;
|
||||
std::cerr << "canhalctrl up <bus name> <type> <interface> [bitrate]" << std::endl;
|
||||
std::cerr << "where:" << std::endl;
|
||||
std::cerr << " bus name - name under which ICanBus will be published" << std::endl;
|
||||
std::cerr << " type - one of: virtual, socketcan, slcan, indexed" << std::endl;
|
||||
std::cerr << " interface - hardware identifier (like can0, vcan0, /dev/ttyUSB0)" << std::endl;
|
||||
std::cerr << " bitrate - such as 100000, 125000, 250000, 500000" << std::endl;
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "canhalctrl down <bus name>" << std::endl;
|
||||
std::cerr << "where:" << std::endl;
|
||||
std::cerr << " bus name - name under which ICanBus will be published" << std::endl;
|
||||
}
|
||||
|
||||
static int up(const std::string& busName, ICanController::InterfaceType type,
|
||||
const std::string& interface, uint32_t bitrate) {
|
||||
bool anySupported = false;
|
||||
for (auto&& service : libcanhaltools::getControlServices()) {
|
||||
auto ctrl = ICanController::getService(service);
|
||||
if (ctrl == nullptr) {
|
||||
std::cerr << "Couldn't open ICanController/" << service;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!libcanhaltools::isSupported(ctrl, type)) continue;
|
||||
anySupported = true;
|
||||
|
||||
ICanController::BusConfig config = {};
|
||||
config.name = busName;
|
||||
config.bitrate = bitrate;
|
||||
|
||||
// TODO(b/146214370): move interfaceId constructors to a library
|
||||
using IfCfg = ICanController::BusConfig::InterfaceId;
|
||||
if (type == ICanController::InterfaceType::VIRTUAL) {
|
||||
config.interfaceId.virtualif({interface});
|
||||
} else if (type == ICanController::InterfaceType::SOCKETCAN) {
|
||||
IfCfg::Socketcan socketcan = {};
|
||||
socketcan.ifname(interface);
|
||||
config.interfaceId.socketcan(socketcan);
|
||||
} else if (type == ICanController::InterfaceType::SLCAN) {
|
||||
IfCfg::Slcan slcan = {};
|
||||
slcan.ttyname(interface);
|
||||
config.interfaceId.slcan(slcan);
|
||||
} else if (type == ICanController::InterfaceType::INDEXED) {
|
||||
unsigned idx;
|
||||
if (!android::base::ParseUint(interface, &idx, unsigned(UINT8_MAX))) {
|
||||
std::cerr << "Interface index out of range: " << idx;
|
||||
return -1;
|
||||
}
|
||||
config.interfaceId.indexed({uint8_t(idx)});
|
||||
} else {
|
||||
CHECK(false) << "Unexpected interface type: " << toString(type);
|
||||
}
|
||||
|
||||
const auto upresult = ctrl->upInterface(config);
|
||||
if (upresult == ICanController::Result::OK) return 0;
|
||||
std::cerr << "Failed to bring interface up: " << toString(upresult) << std::endl;
|
||||
// Let's continue the loop to try other controllers.
|
||||
}
|
||||
|
||||
if (!anySupported) {
|
||||
std::cerr << "No controller supports " << toString(type) << std::endl;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int down(const std::string& busName) {
|
||||
for (auto&& service : libcanhaltools::getControlServices()) {
|
||||
auto ctrl = ICanController::getService(service);
|
||||
if (ctrl == nullptr) continue;
|
||||
|
||||
if (ctrl->downInterface(busName)) return 0;
|
||||
}
|
||||
|
||||
std::cerr << "Failed to bring interface " << busName << " down (maybe it's down already?)"
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static std::optional<ICanController::InterfaceType> parseInterfaceType(const std::string& str) {
|
||||
if (str == "virtual") return ICanController::InterfaceType::VIRTUAL;
|
||||
if (str == "socketcan") return ICanController::InterfaceType::SOCKETCAN;
|
||||
if (str == "slcan") return ICanController::InterfaceType::SLCAN;
|
||||
if (str == "indexed") return ICanController::InterfaceType::INDEXED;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static int main(int argc, char* argv[]) {
|
||||
base::SetDefaultTag("CanHalControl");
|
||||
base::SetMinimumLogSeverity(android::base::VERBOSE);
|
||||
|
||||
if (argc == 0) {
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string cmd(argv[0]);
|
||||
argv++;
|
||||
argc--;
|
||||
|
||||
if (cmd == "up") {
|
||||
if (argc < 3 || argc > 4) {
|
||||
std::cerr << "Invalid number of arguments to up command: " << argc << std::endl;
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::string busName(argv[0]);
|
||||
const std::string typeStr(argv[1]);
|
||||
const std::string interface(argv[2]);
|
||||
|
||||
const auto type = parseInterfaceType(typeStr);
|
||||
if (!type) {
|
||||
std::cerr << "Invalid interface type: " << typeStr << std::endl;
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t bitrate = 0;
|
||||
if (argc == 4 && !android::base::ParseUint(argv[3], &bitrate)) {
|
||||
std::cerr << "Invalid bitrate!" << std::endl;
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return up(busName, *type, interface, bitrate);
|
||||
} else if (cmd == "down") {
|
||||
if (argc != 1) {
|
||||
std::cerr << "Invalid number of arguments to down command: " << argc << std::endl;
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return down(argv[0]);
|
||||
} else {
|
||||
std::cerr << "Invalid command: " << cmd << std::endl;
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 1) return -1;
|
||||
return ::android::hardware::automotive::can::main(--argc, ++argv);
|
||||
}
|
||||
133
automotive/can/1.0/tools/canhaldump.cpp
Normal file
133
automotive/can/1.0/tools/canhaldump.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICanBus.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICanMessageListener.h>
|
||||
#include <android/hidl/manager/1.2/IServiceManager.h>
|
||||
#include <hidl-utils/hidl-utils.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace android::hardware::automotive::can {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using ICanBus = V1_0::ICanBus;
|
||||
using Result = V1_0::Result;
|
||||
|
||||
struct CanMessageListener : public V1_0::ICanMessageListener {
|
||||
const std::string name;
|
||||
|
||||
CanMessageListener(std::string name) : name(name) {}
|
||||
|
||||
virtual Return<void> onReceive(const V1_0::CanMessage& message) {
|
||||
int msgIdWidth = 3;
|
||||
if (message.isExtendedId) msgIdWidth = 8;
|
||||
std::cout << " " << name << " " << std::hex << std::uppercase << std::setw(msgIdWidth)
|
||||
<< std::setfill('0') << message.id << std::setw(0);
|
||||
std::cout << " [" << message.payload.size() << "] ";
|
||||
if (message.remoteTransmissionRequest) {
|
||||
std::cout << "remote request";
|
||||
} else {
|
||||
for (const auto byte : message.payload) {
|
||||
std::cout << " " << std::setfill('0') << std::setw(2) << unsigned(byte);
|
||||
}
|
||||
}
|
||||
std::cout << std::nouppercase << std::dec << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual Return<void> onError(V1_0::ErrorEvent error) {
|
||||
std::cout << " " << name << " " << toString(error) << std::endl;
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
static void usage() {
|
||||
std::cerr << "canhaldump - dump CAN bus traffic" << std::endl;
|
||||
std::cerr << std::endl << "usage:" << std::endl << std::endl;
|
||||
std::cerr << "canhaldump <bus name>" << std::endl;
|
||||
std::cerr << "where:" << std::endl;
|
||||
std::cerr << " bus name - name under which ICanBus is be published" << std::endl;
|
||||
}
|
||||
|
||||
// TODO(b/135918744): extract to a new library
|
||||
static sp<ICanBus> tryOpen(const std::string& busname) {
|
||||
auto bus = ICanBus::tryGetService(busname);
|
||||
if (bus != nullptr) return bus;
|
||||
|
||||
/* Fallback for interfaces not registered in manifest. For testing purposes only,
|
||||
* one should not depend on this in production deployment. */
|
||||
auto manager = hidl::manager::V1_2::IServiceManager::getService();
|
||||
auto ret = manager->get(ICanBus::descriptor, busname).withDefault(nullptr);
|
||||
if (ret == nullptr) return nullptr;
|
||||
|
||||
std::cerr << "WARNING: bus " << busname << " is not registered in device manifest, "
|
||||
<< "trying to fetch it directly..." << std::endl;
|
||||
|
||||
return ICanBus::castFrom(ret);
|
||||
}
|
||||
|
||||
static int candump(const std::string& busname) {
|
||||
auto bus = tryOpen(busname);
|
||||
if (bus == nullptr) {
|
||||
std::cerr << "Bus " << busname << " is not available" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Result result;
|
||||
sp<V1_0::ICloseHandle> chnd;
|
||||
// TODO(b/135918744): extract to library
|
||||
bus->listen({}, new CanMessageListener(busname), hidl_utils::fill(&result, &chnd)).assertOk();
|
||||
|
||||
if (result != Result::OK) {
|
||||
std::cerr << "Listen call failed: " << toString(result) << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (true) std::this_thread::sleep_for(1h);
|
||||
}
|
||||
|
||||
static int main(int argc, char* argv[]) {
|
||||
base::SetDefaultTag("CanHalDump");
|
||||
base::SetMinimumLogSeverity(android::base::VERBOSE);
|
||||
|
||||
if (argc == 0) {
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc != 1) {
|
||||
std::cerr << "Invalid number of arguments" << std::endl;
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return candump(argv[0]);
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 1) return -1;
|
||||
return ::android::hardware::automotive::can::main(--argc, ++argv);
|
||||
}
|
||||
153
automotive/can/1.0/tools/canhalsend.cpp
Normal file
153
automotive/can/1.0/tools/canhalsend.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/parseint.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICanBus.h>
|
||||
#include <android/hidl/manager/1.2/IServiceManager.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace android::hardware::automotive::can {
|
||||
|
||||
using ICanBus = V1_0::ICanBus;
|
||||
using Result = V1_0::Result;
|
||||
|
||||
static void usage() {
|
||||
std::cerr << "canhalsend - simple command line tool to send raw CAN frames" << std::endl;
|
||||
std::cerr << std::endl << "usage:" << std::endl << std::endl;
|
||||
std::cerr << "canhalsend <bus name> <can id>#<data>" << std::endl;
|
||||
std::cerr << "where:" << std::endl;
|
||||
std::cerr << " bus name - name under which ICanBus is published" << std::endl;
|
||||
std::cerr << " can id - such as 1a5 or 1fab5982" << std::endl;
|
||||
std::cerr << " data - such as deadbeef, 010203, or R for a remote frame" << std::endl;
|
||||
}
|
||||
|
||||
// TODO(b/135918744): extract to a new library
|
||||
static sp<ICanBus> tryOpen(const std::string& busname) {
|
||||
auto bus = ICanBus::tryGetService(busname);
|
||||
if (bus != nullptr) return bus;
|
||||
|
||||
/* Fallback for interfaces not registered in manifest. For testing purposes only,
|
||||
* one should not depend on this in production deployment. */
|
||||
auto manager = hidl::manager::V1_2::IServiceManager::getService();
|
||||
auto ret = manager->get(ICanBus::descriptor, busname).withDefault(nullptr);
|
||||
if (ret == nullptr) return nullptr;
|
||||
|
||||
std::cerr << "WARNING: bus " << busname << " is not registered in device manifest, "
|
||||
<< "trying to fetch it directly..." << std::endl;
|
||||
|
||||
return ICanBus::castFrom(ret);
|
||||
}
|
||||
|
||||
static int cansend(const std::string& busname, const V1_0::CanMessage& msg) {
|
||||
auto bus = tryOpen(busname);
|
||||
if (bus == nullptr) {
|
||||
std::cerr << "Bus " << busname << " is not available" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto result = bus->send(msg);
|
||||
if (result != Result::OK) {
|
||||
std::cerr << "Send call failed: " << toString(result) << std::endl;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::optional<V1_0::CanMessage> parseCanMessage(const std::string& msg) {
|
||||
const auto hashpos = msg.find("#");
|
||||
if (hashpos == std::string::npos) return std::nullopt;
|
||||
|
||||
const std::string msgidStr = msg.substr(0, hashpos);
|
||||
const std::string payloadStr = msg.substr(hashpos + 1);
|
||||
|
||||
V1_0::CanMessageId msgid;
|
||||
// "0x" must be prepended to msgidStr, since ParseUint doesn't accept a base argument.
|
||||
if (!android::base::ParseUint("0x" + msgidStr, &msgid)) return std::nullopt;
|
||||
|
||||
V1_0::CanMessage canmsg = {};
|
||||
canmsg.id = msgid;
|
||||
if (msgid > 0x7FF) {
|
||||
canmsg.isExtendedId = true;
|
||||
}
|
||||
|
||||
if (android::base::StartsWith(payloadStr, "R")) {
|
||||
canmsg.remoteTransmissionRequest = true;
|
||||
|
||||
/* The CAN bus HAL doesn't define a data length code (DLC) field, since it is inferrred
|
||||
* from the payload size. RTR messages indicate to the receiver how many bytes they are
|
||||
* expecting to receive back via the DLC sent with the RTR frame. */
|
||||
if (payloadStr.size() <= 1) return canmsg;
|
||||
|
||||
unsigned int dlc = 0;
|
||||
|
||||
/* The maximum DLC for CAN-FD is 64 bytes and CAN 2.0 is 8 bytes. Limit the size of the DLC
|
||||
* to something memory safe and let the HAL determine if the DLC is valid. */
|
||||
if (!android::base::ParseUint(payloadStr.substr(1), &dlc, 10000u)) {
|
||||
std::cerr << "Invalid DLC for RTR frame!" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
canmsg.payload.resize(dlc);
|
||||
return canmsg;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> payload;
|
||||
if (payloadStr.size() % 2 != 0) return std::nullopt;
|
||||
for (size_t i = 0; i < payloadStr.size(); i += 2) {
|
||||
std::string byteStr(payloadStr, i, 2);
|
||||
uint8_t byteBuf;
|
||||
if (!android::base::ParseUint("0x" + byteStr, &byteBuf)) return std::nullopt;
|
||||
payload.emplace_back(byteBuf);
|
||||
}
|
||||
canmsg.payload = payload;
|
||||
|
||||
return canmsg;
|
||||
}
|
||||
|
||||
static int main(int argc, char* argv[]) {
|
||||
base::SetDefaultTag("CanHalSend");
|
||||
base::SetMinimumLogSeverity(android::base::VERBOSE);
|
||||
|
||||
if (argc == 0) {
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argc != 2) {
|
||||
std::cerr << "Invalid number of arguments" << std::endl;
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string busname(argv[0]);
|
||||
const auto canmsg = parseCanMessage(argv[1]);
|
||||
if (!canmsg) {
|
||||
std::cerr << "Failed to parse CAN message argument" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cansend(busname, *canmsg);
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 1) return -1;
|
||||
return ::android::hardware::automotive::can::main(--argc, ++argv);
|
||||
}
|
||||
34
automotive/can/1.0/tools/configurator/Android.bp
Normal file
34
automotive/can/1.0/tools/configurator/Android.bp
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright (C) 2020 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
cc_binary {
|
||||
name: "canhalconfigurator",
|
||||
init_rc: ["canhalconfigurator.rc"],
|
||||
defaults: ["android.hardware.automotive.can@defaults"],
|
||||
srcs: [
|
||||
"canhalconfigurator.cpp",
|
||||
"canprototools.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"android.hardware.automotive.can@1.0",
|
||||
"libhidlbase",
|
||||
"libprotobuf-cpp-full",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.automotive.can@1.x-config-format",
|
||||
"android.hardware.automotive.can@libcanhaltools",
|
||||
],
|
||||
}
|
||||
103
automotive/can/1.0/tools/configurator/canhalconfigurator.cpp
Normal file
103
automotive/can/1.0/tools/configurator/canhalconfigurator.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "canbus_config.pb.h"
|
||||
#include "canprototools.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <android/hardware/automotive/can/1.0/ICanController.h>
|
||||
#include <libcanhaltools/libcanhaltools.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace android::hardware::automotive::can {
|
||||
|
||||
using ICanController = V1_0::ICanController;
|
||||
|
||||
/**
|
||||
* Takes output from parsed protobuf config and uses it to configure the CAN HAL.
|
||||
*
|
||||
* \param pb_cfg is an instance of the autogenerated protobuf object for our configuration.
|
||||
* \return boolean status, true on success, false on failure.
|
||||
*/
|
||||
static bool processPbCfg(const config::CanBusConfig& pb_cfg) {
|
||||
for (auto const& bus : pb_cfg.buses()) {
|
||||
if (bus.name().empty()) {
|
||||
LOG(ERROR) << "Invalid config: Bus config must have a valid name field";
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Configure " << bus.name();
|
||||
auto bus_cfg = config::fromPbBus(bus);
|
||||
if (!bus_cfg.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(149405589): remove this sleep and associated includes.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
if (libcanhaltools::configureIface(*bus_cfg) != ICanController::Result::OK) {
|
||||
LOG(ERROR) << "No controller supports " << bus.name() << std::endl;
|
||||
// TODO(149405589): add retry logic in case a bus fails to come up.
|
||||
continue;
|
||||
}
|
||||
LOG(INFO) << bus.name() << " has been successfully configured!";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This kicks off the CAN HAL configuration process. This starts the following:
|
||||
* 1. Reading the config file
|
||||
* 2. Setting up CAN buses
|
||||
* 3. Handling services
|
||||
* \param filepath is a string specifying the absolute path of the config file
|
||||
* \return boolean status, true on success, false on failure
|
||||
*/
|
||||
static bool configuratorStart(const std::string& filepath) {
|
||||
base::SetDefaultTag("CanConfigurator");
|
||||
|
||||
auto pb_cfg = config::parseConfigFile(filepath);
|
||||
if (!pb_cfg.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// process the rest of the config file data and configure the CAN buses.
|
||||
if (!processPbCfg(*pb_cfg)) {
|
||||
return false;
|
||||
}
|
||||
LOG(INFO) << "CAN HAL has been configured!";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::string config_filepath = "/etc/canbus_config.pb";
|
||||
|
||||
// allow for CLI specification of a config file.
|
||||
if (argc == 2) {
|
||||
config_filepath = argv[1];
|
||||
} else if (argc > 2) {
|
||||
std::cerr << "usage: " << argv[0] << " [optional config filepath]";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!::android::hardware::automotive::can::configuratorStart(config_filepath)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
service canhalconfigurator /system/bin/canhalconfigurator
|
||||
class core
|
||||
oneshot
|
||||
156
automotive/can/1.0/tools/configurator/canprototools.cpp
Normal file
156
automotive/can/1.0/tools/configurator/canprototools.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "canprototools.h"
|
||||
|
||||
#include <android-base/logging.h>
|
||||
#include <google/protobuf/io/zero_copy_stream_impl.h>
|
||||
#include <google/protobuf/text_format.h>
|
||||
#include <hidl/HidlTransportSupport.h>
|
||||
#include <libcanhaltools/libcanhaltools.h>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace android::hardware::automotive::can::config {
|
||||
|
||||
using ICanController = V1_0::ICanController;
|
||||
|
||||
/**
|
||||
* Helper function for parseConfigFile. readString is used to get the fist n characters (n) from an
|
||||
* istream object (s) and return it as a string object.
|
||||
*
|
||||
* \param s istream of the file you intend to read.
|
||||
* \param n streamsize object of the number of characters you'd like.
|
||||
* \return optional string containing up to n characters from the stream(s) you provided.
|
||||
*/
|
||||
static std::optional<std::string> readString(std::istream& s, std::streamsize n) {
|
||||
char buff[n];
|
||||
auto got = s.read(buff, n).gcount();
|
||||
if (!s.good() && !s.eof()) return std::nullopt;
|
||||
return std::string(buff, 0, std::min(n, got));
|
||||
}
|
||||
|
||||
std::optional<CanBusConfig> parseConfigFile(const std::string& filepath) {
|
||||
std::ifstream cfg_stream(filepath);
|
||||
|
||||
// text headers that would be present in a plaintext proto config file.
|
||||
static const std::array<std::string, 3> text_headers = {"buses", "#", "controller"};
|
||||
auto cfg_file_snippet = readString(cfg_stream, 10);
|
||||
|
||||
if (!cfg_file_snippet.has_value()) {
|
||||
LOG(ERROR) << "Can't open " << filepath << " for reading";
|
||||
return std::nullopt;
|
||||
}
|
||||
cfg_stream.seekg(0);
|
||||
|
||||
// check if any of the textHeaders are at the start of the config file.
|
||||
bool text_format = false;
|
||||
for (auto const& header : text_headers) {
|
||||
if (cfg_file_snippet->compare(0, header.length(), header) == 0) {
|
||||
text_format = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CanBusConfig config;
|
||||
if (text_format) {
|
||||
google::protobuf::io::IstreamInputStream pb_stream(&cfg_stream);
|
||||
if (!google::protobuf::TextFormat::Parse(&pb_stream, &config)) {
|
||||
LOG(ERROR) << "Failed to parse (text format) " << filepath;
|
||||
return std::nullopt;
|
||||
}
|
||||
} else if (!config.ParseFromIstream(&cfg_stream)) {
|
||||
LOG(ERROR) << "Failed to parse (binary format) " << filepath;
|
||||
return std::nullopt;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
std::optional<ICanController::BusConfig> fromPbBus(const Bus& pb_bus) {
|
||||
ICanController::BusConfig bus_cfg = {};
|
||||
bus_cfg.name = pb_bus.name();
|
||||
|
||||
switch (pb_bus.iface_type_case()) {
|
||||
case Bus::kNative: {
|
||||
const auto ifname = pb_bus.native().ifname();
|
||||
const auto serialno = pb_bus.native().serialno();
|
||||
if (ifname.empty() == serialno.empty()) {
|
||||
LOG(ERROR) << "Invalid config: native type bus must have an iface name xor a "
|
||||
<< "serial number";
|
||||
return std::nullopt;
|
||||
}
|
||||
bus_cfg.bitrate = pb_bus.bitrate();
|
||||
ICanController::BusConfig::InterfaceId::Socketcan socketcan = {};
|
||||
if (!ifname.empty()) socketcan.ifname(ifname);
|
||||
if (!serialno.empty()) socketcan.serialno({serialno.begin(), serialno.end()});
|
||||
bus_cfg.interfaceId.socketcan(socketcan);
|
||||
break;
|
||||
}
|
||||
case Bus::kSlcan: {
|
||||
const auto ttyname = pb_bus.slcan().ttyname();
|
||||
const auto serialno = pb_bus.slcan().serialno();
|
||||
if (ttyname.empty() == serialno.empty()) {
|
||||
LOG(ERROR) << "Invalid config: slcan type bus must have a tty name";
|
||||
return std::nullopt;
|
||||
}
|
||||
bus_cfg.bitrate = pb_bus.bitrate();
|
||||
ICanController::BusConfig::InterfaceId::Slcan slcan = {};
|
||||
if (!ttyname.empty()) slcan.ttyname(ttyname);
|
||||
if (!serialno.empty()) slcan.serialno({serialno.begin(), serialno.end()});
|
||||
bus_cfg.interfaceId.slcan(slcan);
|
||||
break;
|
||||
}
|
||||
case Bus::kVirtual: {
|
||||
// Theoretically, we could just create the next available vcan iface.
|
||||
const auto ifname = pb_bus.virtual_().ifname();
|
||||
if (ifname.empty()) {
|
||||
LOG(ERROR) << "Invalid config: native type bus must have an iface name";
|
||||
return std::nullopt;
|
||||
}
|
||||
bus_cfg.interfaceId.virtualif({ifname});
|
||||
break;
|
||||
}
|
||||
case Bus::kIndexed: {
|
||||
const auto index = pb_bus.indexed().index();
|
||||
if (index > UINT8_MAX) {
|
||||
LOG(ERROR) << "Interface index out of range: " << index;
|
||||
return std::nullopt;
|
||||
}
|
||||
bus_cfg.interfaceId.indexed({uint8_t(index)});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(ERROR) << "Invalid config: bad interface type for " << bus_cfg.name;
|
||||
return std::nullopt;
|
||||
}
|
||||
return bus_cfg;
|
||||
}
|
||||
|
||||
std::optional<ICanController::InterfaceType> getHalIftype(const Bus& pb_bus) {
|
||||
switch (pb_bus.iface_type_case()) {
|
||||
case Bus::kNative:
|
||||
return ICanController::InterfaceType::SOCKETCAN;
|
||||
case Bus::kSlcan:
|
||||
return ICanController::InterfaceType::SLCAN;
|
||||
case Bus::kVirtual:
|
||||
return ICanController::InterfaceType::VIRTUAL;
|
||||
case Bus::kIndexed:
|
||||
return ICanController::InterfaceType::INDEXED;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android::hardware::automotive::can::config
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user