Getting Started with Chamelium V3
🎉 Congratulations on receiving your Chamelium V3 (Cv3) device.🎊 If you made it here, you're one of the lucky ones who received the all-new Cv3 device.🥳
Please follow the following steps to successfully setup your Cv3 experience:
1. Check Your Package
Every Chamelium package should include the Cv3 board (yellow) and a power adapter.
2. Join the Chamelium Group
Head to Chameleon-v3-users-external and request to join the group. This is where you'll find relevant discussions about Chamelium, and where you can ask any questions about it and someone familiar with the topic would be able to help out. New Releases will also be announced on this group. Releases are sent out regularly with the latest firmware and daemon updates. Example of the latest release email.
3. Setup Your New Shiny Device
To setup your Cv3, head to Chameleon v3 Setup, Deployment, and Testing. The doc should include everything you need to know to setup your device with the latest release.
4. Test Out Cv3 with Your DUT
Before you run any tests, let's make sure Cv3 runs well and is reachable. We will interact manually with Cv3 to validate everything
a. Grab Cv3's IP
Cv3 advertises its IP on boot. If you don't know the IP, reset Cv3's power and follow steps 4-5 in the setup doc: Boot the Linux System
I recommend you save it in your ~/.ssh/config
for posterity
Host cv3
HostName 123.45.678.910 # the IP you grabbed above
User cros
StrictHostKeyChecking no
VerifyHostKeyDNS no
CheckHostIP no
UserKnownHostsFile /dev/null
IdentitiesOnly yes
IdentityFile %d/.ssh/testing_rsa # https://chromium.googlesource.com/chromiumos/chromite/+/master/ssh_keys/testing_rsa # nocheck
b. Understand the Ports
While the info looks a bit daunting as you're about to get started, the Single Port subsection below is important to get your validation in place. The Hardware Cv3 has 4 physical ports to physically plug
+--------+---------+
| DP 1 | DP 2 |
+--------+---------+
| HDMI 1 | HDMI 2 |
+--------+---------+
Single Port
If you're using the most simple case, you can only active in 1 at a time as the ITE ship can only drive 1 port.
You can physically connected as many as you want, but you can only tell Cv3 to "active" (Plug()
in Cv3 lingo) 1 ITE port.
[PORT NAME] = PORT ID
ITE_DP1 = 0
ITE_DP2 = 1
ITE_HDMI1 = 2
ITE_HDMI2 = 3
...
Multiple Ports If you require more than 1 physically plugged monitors to be active at the same time, Cv3 can support more ports, but we will have to use different port IDs.
...
ITE_HDMI1 = 2
ITE_HDMI2 = 3
FPGA_DP1 = 4
FPGA_DP2 = 5
...
The ITE ship can still support only 1 display, so you can only get 1 HDMI, but the FPGA chip can support multiple physical displays, so the most you can do here is 3 simultaneous ports: (ITE_HDMI1|2
), FPGA_DP1
and FPGA_DP2
.
NOTE: Physically speaking, ITE_DP1
&FPGA_DP1
relate to the same physical port (same for *_DP2), but the port ID is what tells Chameleon whether it's able to drive multiple displays and reroutes the correct hardware.
Activate a Port
To "turn on" a port, 2 steps are needed: Setting an EDID and Plugging the port. You can't plug a port without setting an EDID.
There is no default EDIDs set. After Reset()
all EDIDs are cleared. Test code needs to explicitly call ApplyEdid()
before plugging the port.
If you don't care what EDID is set, there is a predefined EDID 0 ready to be applied so CreateEdid()
call can be skipped and following pattern can be used:
X = chameleond.GetSupportedPorts()[0] # discover any port, here I take the first one
chameleond.ApplyEdid(X, 0)
chameleond.Plug(X)
MST The discussion is long, so jump to Understand MST section below
c. Test Out the Screenshot Tool
- Connect your DUT to Cv3. Any port is fine but make note of the Cv3 physical port and port ID you're connected to.
- Your DUT may or may not recognize Cv3, depending on the port plug status. If it recognizes it as an external monitor, that's great news. If it doesn't, don't worry about it, we're here to test it.
- Run the Screenshot Tool as described in the setup doc: Interact with chameleond directly
- If it succeeds and you get a screenshot of your external display (the photo result will be saved in the directory where you called
screenshot
), then your Cv3 is reachable by the DUT and the ports are functional. - If that doesn't work, let's interactively play with it and see if it's reachable.
d. Interactively Play with Cv3
- If you're on corp network, follow the same workaround here
ssh cv3 -L 9992:localhost:9992
- In your chromeos repo, navigate to
cd src/platform/chameleon
- call
python client/test_server.py --chameleon_host cv3
(on corp, this would bepython client/test_server.py --chameleon_host localhost
) - From the interactive shell, plug the display you want
>>> p.Plug(0)
If you can't do that, then your Cv3 is unreachable. Make sure it's on and you can ssh to it
NOTE: This interactive shell can be run from within Chamelium itself
ssh cv3
python chameleon/client/test_server.py --chameleon_host localhost
>>> p.ApplyEdid(0, 0)
>>> p.Plug(0)
All calls are found in the interface file
5. Understand MST
With V3 that now supports MST, things get a bit more fancy and ports are a bit more dynamic than described earlier. Here is explanation for the current state:
[PORT NAME] = PORT ID
ITE_DP1 = 0
ITE_DP2 = 1
ITE_HDMI1 = 2
ITE_HDMI2 = 3
FPGA_DP1 = 4
FPGA_DP2 = 5
FPGA_DP2_MST0 = 5
FPGA_DP2_MST1 = 6
FPGA_DP2_MST2 = 7
FPGA_DP2_MST3 = 8
NOTE: For tests, while the ports are constant right now, it is better to use other properties (such as GetConnectorType()) or iterate over ports instead of hardcoding these numbers. Port Conflict and Discoverability
def IsConflict(self, port_id_a, port_id_b)
def GetMutuallyExclusivePorts(self, port_id)
IsConflict()
allows to discover if two particular ports can be plugged at the same time and GetMutuallyExclusivePorts
discovers all ports that will be in conflict. Tests should check for this before plugging multiple ports at the same time.
Assume portA is in conflict with portB and portA is plugged. When you try to plug portB, Chameleond will raise an exception. portA should be explicitly unplugged first so plugging portB will succeed.
def GetPortName(self, port_id)
This is an API for interactive users to get human friendly name e.g. FPGA DP1 (left)
or ITE DP2 (right)
The MST Port
def IsMst(self, port_id)
MST ports are NOT discoverable by GetSupportedPorts
. Only FPGA_DP2
can be discovered because it also functions as a standalone port. IsMst
is introduced to test if port is capable of MST. Calling it for IsMst(FPGA_DP2)
will return true
.
To find out how many extra ports can be configured behind the MST port GetChildren
can be used. It will return a tuple of children ports (not discoverable by other APIs).
The MST Children Ports
def GetChildren(self, port_id)
def PlugWithChildren(self, port_id, children_ids)
Calling GetChildren(FPGA_DP2)
return 3 Port IDS: FPGA_DP2_MST1
, FPGA_DP2_MST2
, FPGA_DP2_MST3
so in total there is up to 4 monitors on this port.
Children ports can be used with all APIs except Plug()
and Unplug()
.
To Plug them, call PlugWithChildren
.
For example, if you want to plug MST0
, MST2
and MST3
displays, then do like the following Python Example:
# Example of discovering required ports
mst_port = None
for port in chameleond.GetSupportedPorts():
if chameleond.IsMst(port):
mst_port = port
break
children = chameleond.GetChildren(mst_port)
# Get last two ports
children = children[-2:]
# Assume there is EDID with id X already created
chameleond.ApplyEdid(mst_port, X)
for child in children:
chameleond.ApplyEdid(child, X)
# Finally plug all three monitors
chameleond.PlugWithChildren(mst_port, children)
Note: The first MST port MST0
(parent port) will always be used if MST is used, as it is the port controlling the HPD line.
Using the standard Plug()
on the MST parent will plug just this port without any children. Using PlugWithChildren
on any port with empty children list (i.e. ITE_HDMI1
) will have the same effect as Plug
on that port.
Unplug
def Unplug(mst_port)
To unplug all MST ports just use regular Unplug
.
6. ChameleonD
ChameleonD is the daemon that runs on Cv3 and is responsible for all the heavy lifting. It's the one that interacts with the hardware and exposes the API for the clients to interact with. The daemon is written in Python and can be found in the chameleond directory. This is what you will interact with through the XMLRPC interface calls, or interactively as described above. All available calls are listed here
7. Run Your First Test
The following guide assumes you're familiar with running IGT and can run a test such as kms_atomic
on your DUT.
If not, please refer to the IGT guide to get started. The guide will also help you building and deploying IGT on your DUT if they're not already there.
Configure the device to run a chamelium test
For a chamelium test, you run it like any other IGT test, but there is a one-time setup (per DUT image) that needs to be done to tell your DUT how to find its associated chamelium. MOTE: This is also document in IGT docs
- ssh to your DUT (you would be accessing through root@)
- Remove rootfs verification if you haven't already
(cros device)$ sudo /usr/share/vboot/bin/make_dev_ssd.sh --remove_rootfs_verification -f
- Create a new file on your DUT
~/.igtrc
- Add the following in
.igtrc
(Modify it as needed: especially theURL
(leave the port number as9992
) andChameliumPortID
)
[Common]
# The path to dump frames that fail comparison checks
FrameDumpPath=/tmp
[DUT]
SuspendResumeDelay=15
# IP address of the Chamelium
# The ONLY mandatory field (the rest of the file can be blank yet IGT will be usable)
# Replace the URL below by yours
[Chamelium]
URL=http://192.168.0.36:9992
# The ID of the chamelium port <> DUT port mapping
# DUT port can be found using `modetest -c`
# Replace the IDs below by yours
# It's an optional field. When no set, Chamelium will perform autodiscovery.
#[Chamelium:DP-4]
#ChameliumPortID=0
The steps above can be generated automatically to you using generate_igtrc.sh
Bash functions
If you're building using cros_sdk
, you can add the following functions to your .bashrc
to make your life easier.
The following assumes you did cros-workon
for IGT.
- Build and deploy to the DUT all of IGT, useful when you change more than 1 test and/or a library/helper
igtdeploy() { # expecting dest
local dest="${1:-"crbook"}"
cros_sdk emerge-${BOARD} igt-gpu-tools && cros_sdk cros deploy ${dest} igt-gpu-tools --root /usr/local
}
- Build and deploy a single IGT test
igtscp() { # expecting test name and dest
local dest="${2:-"crbook"}"
cros_sdk emerge-${BOARD} igt-gpu-tools && scp /sda/cros/chroot/build/${BOARD}/usr/libexec/igt-gpu-tools/$1 ${dest}:///usr/local/libexec/igt-gpu-tools
}
- When using a new device, build all dependencies needed for subsequent IGT builds
igtdeploydeps() { #expecting DUT
local dest="${1:-"crbook"}" #crbook is my default DUT hostname
cros_sdk emerge-${BOARD} sys-libs/llvm-libunwind && cros_sdk cros deploy ${dest} sys-libs/llvm-libunwind &
cros_sdk emerge-${BOARD} x11-libs/libpciaccess && cros_sdk cros deploy ${dest} x11-libs/libpciaccess &
cros_sdk emerge-${BOARD} dev-libs/xmlrpc-c && cros_sdk cros deploy ${dest} dev-libs/xmlrpc-c &
cros_sdk emerge-${BOARD} sci-libs/gsl && cros_sdk cros deploy ${dest} sci-libs/gsl &
wait
igtdeploy $1
}
- rebase my current work on the latest master # nocheck
igtsync() {
local br_name=$(git rev-parse --abbrev-ref HEAD)
local old_stash_count=$(git stash list | wc -l)
git stash
git checkout upstream/master # nocheck
git pull
git checkout $br_name
git rebase upstream/master # nocheck
local new_stash_count=$(git stash list | wc -l)
if [ $old_stash_count -lt $new_stash_count ]
then
git stash pop
fi
}